Compare commits

...
Author SHA1 Message Date
Rob Bygrave d4e72eefa0 [maven-release-plugin] prepare release ebean-11.12.1 2018-03-01 13:43:14 +13:00
Rob Bygrave 525afbc00f #1296 - where().exampleLike(bean) ... with 'empty bean' produces invalid SQL 2018-03-01 13:38:09 +13:00
Rob Bygrave 6f2e1c0248 #1295 - Multiple platform DbMigration generation with non-native boolean type (BIT, INTEGER etc) produces incorrect DDL for other DB Platform with native boolean 2018-03-01 12:55:22 +13:00
Rob Bygrave 0d0a3422fc #1283 - ENH: Support simplified test configuration (with docker setup typically via application-test.yml)
Refactor tidy - no effective change on this commit.
2018-02-28 21:11:49 +13:00
Rob Bygrave 57b0190cd2 #1283 - ENH: Support simplified test configuration (with docker setup typically via application-test.yml) 2018-02-27 18:28:03 +13:00
Rob Bygrave c8e199177c #1294 - NPE when delete cascade from bean with SoftDelete to imported OneToOne without SoftDelete 2018-02-27 15:31:26 +13:00
Rob Bygrave 68a470a086 Test change only - No effective change, remove noop ServerConfig.loadTestProperties() 2018-02-27 15:00:13 +13:00
Rob Bygrave 45a023f0e4 #1282 - Not supported for @DbArray mapping when using kotlin var instead of val 2018-02-27 14:25:44 +13:00
Rob Bygrave 06bce35f5a #1293 - Refactor - move JdbcClose into io.ebean.util ... from io.ebeaninternal.util as it is used in public API (platform code, sequences etc) 2018-02-27 12:56:20 +13:00
Roland PramlandRob Bygrave 3dfdd0e2e8 #1292 - Fix for non-native / number boolean types (MySql, SqlServer, Oracle, Sqlite, SqlAnywhere) removing "default 0" + Fix for DB2 add foreign key update clause 2018-02-27 11:58:10 +13:00
Rob Bygrave a569c7fc96 #1275 - Additional refactor of DefaultOrmQuery.prepareForPaging() for - Add ServerConfig.setDefaultOrderById() ...
Remove use of SpiOrmQueryRequest<T> request
2018-02-27 11:35:10 +13:00
Rob Bygrave e751f991cb #1275 - Additional refactor for - Add ServerConfig.setDefaultOrderById() ...
Removes the cast on DefaultOrmQuery.prepare()
2018-02-27 11:31:30 +13:00
Roland PramlandRob Bygrave 83cf768369 Append preconfigured database to dbMigration (#1291) 2018-02-27 11:09:44 +13:00
Roland PramlandRob Bygrave bb418456e3 Misc fixes (#1290) 2018-02-27 11:06:35 +13:00
Roland PramlandRob Bygrave ac9f3408b2 FIX: reset cache statistics (fixes test cases in eclipse when run in different order) (#1289) 2018-02-27 11:03:27 +13:00
Roland PramlandRob Bygrave 78f8d48e2e FIX: more timing issues - mysql does not support resolution in millis (#1288) 2018-02-27 11:00:58 +13:00
Roland PramlandRob Bygrave 3f8b9529e1 Fix clock sync (#1287)
Test only change - Fix clock sync timing issues with tests using -DdbClockDelta
2018-02-27 10:59:19 +13:00
Roland PramlandRob Bygrave 10f3d85d9f No effective code change - add serialize/serialversionuid (#1286) 2018-02-27 10:57:07 +13:00
Roland PramlandRob Bygrave 0e3f47ee82 No effective code change: Fixwarnings, overrides, generics etc. (#1285) 2018-02-27 10:15:19 +13:00
Roland PramlandRob Bygrave cdbbd749a3 FIX: h2Merge test - which may fail due timing issues (#1284)
Test only change - FIX: h2Merge test - which may fail due timing issues
2018-02-27 10:11:10 +13:00
Roland PramlandRob Bygrave a712be2917 auomatically append "order by id" when paging and query.orderById = true (#1275)
Add ServerConfig.setDefaultOrderById() and query.orderById(boolean) ... to control automatically append "order by id" with paging query
2018-02-27 10:09:52 +13:00
Rob Bygrave 0cba0664f8 #1281 - Test change only - default back from sql server to h2 2018-02-26 15:06:17 +13:00
Rob Bygrave a3e5c3a786 #1281 - Test change only - Update DbMigrationTest for SQL Server using SEQUENCE based Identity 2018-02-26 15:04:57 +13:00
Rob Bygrave 58def02cd1 #1280 - Update test for - Remove "order by" clause of RawSql query when executing findCount()
The code fix associated with this is in CQueryBuilder line 276
2018-02-26 14:41:56 +13:00
Rob Bygrave cec65cc888 #1279 - Change ObjectFactory method to match, tidy format (no real change here) 2018-02-26 14:32:38 +13:00
Roland PramlandRob Bygrave da7bb834e7 DbMigration supports altering UniqueConstraints and foreign keys (#1279)
* NEW: Ebean reads version from pop.properties and prints it at start up

* DDL generation and migration can append a header and platformMigration is prepared to append epilog & prolog to scripts

* FIX: Set identity only, if it is not the platform default type

* Updated reference scripts

* ADD basic support for foreign key migration

* DbMigration detects alter foreign keys

* tests
2018-02-26 14:24:52 +13:00
Rob Bygrave a64a244745 Bump pom to 11.12.1-SNAPSHOT (Event listener change #1063, Yaml properties change #1246) 2018-02-26 13:26:26 +13:00
Rob Bygrave 1c19d4cf7e #1278 - Multi-tenant delete by id ... doesn't return 0 when no rows deleted
Where -1 (jdbc batch) is returned as true as per current behaviour
2018-02-26 13:20:49 +13:00
Rob Bygrave 347225efb2 #1278 - Multi-tenant delete by id ... doesn't return 0 when no rows deleted 2018-02-26 13:16:20 +13:00
Roland PramlandRob Bygrave 91cb5a23fd Fix dbmigration (#1277)
ENH: Support adding a '-- Generated by' comment to DDL including version and timestamp
2018-02-26 12:37:54 +13:00
Rob Bygrave 0e219a9bc6 Revert #1263 - ENH: Make Transaction API Fluid such that we can set multiple thing like batchSize and label in a fluid fashion #1276 2018-02-26 11:45:49 +13:00
Rob Bygrave 21182da793 Fix test LoaderTest 2018-02-26 09:18:45 +13:00
Roland PramlandRob Bygrave 0de084caec removed escapeLikeCharacter method, as it is covered by parent implementation (#1274) 2018-02-26 08:42:18 +13:00
Roland PramlandRob Bygrave e3425a8850 FIX: possible resource leaking in testcases (all created transactions are closed by followed try statement) (#1273) 2018-02-26 08:40:53 +13:00
Rob Bygrave 8e9843ab8a #1246 - Support use of application.yml and application-test.yml ... (as alternative to ebean.properties, test-ebean.properties) 2018-02-26 08:35:47 +13:00
Rob Bygrave 75462e7410 #1272 - ENH: Add built in support for Joda Period and java time Period 2018-02-23 17:54:02 +13:00
Rob Bygrave 265bda8cf2 #1272 - ENH: Add built in support for Joda Period 2018-02-23 17:41:01 +13:00
Rob Bygrave 7d0ed261b5 #1271 - ENH: Add BeanFinder and BeanRepository ... such that there is a read to go base class for DI repository style 2018-02-23 16:55:07 +13:00
Rob Bygrave ff111eaf33 #1063 - Add Event Listeners capable of being notified for delete by id
Adds BeanDeleteIdRequest and adds a method to BeanPersistController and BeanPersistAdapter
2018-02-23 14:34:08 +13:00
Rob Bygrave 8b18e907af #1063 - Add Event Listeners capable of being notified for delete by query or update by query
Adds QueryType and adds method query.getQueryType() which then can be used in a BeanQueryAdapter
2018-02-23 14:04:36 +13:00
Rob Bygrave 40704e2ef7 No Effective change - change test to use assertJ 2018-02-23 13:37:43 +13:00
Rob Bygrave 0e62d0579e [maven-release-plugin] prepare for next development iteration 2018-02-23 12:50:59 +13:00
Rob Bygrave 47d55a2f29 [maven-release-plugin] prepare release ebean-11.11.1 2018-02-23 12:50:45 +13:00
Rob Bygrave b037be5d3e No Effective change - bump agent in pom (with query bean enhancement of interface default methods) 2018-02-23 12:48:06 +13:00
Rob Bygrave 60a3c24cba No Effective change - Tidy pom removing commented out old sqlserver jdbc driver 2018-02-23 10:31:22 +13:00
Rob Bygrave 4a243a76a7 #1270 - Multi-tenant - partition mode - Delete by id does not include tenantId value in predicate 2018-02-23 10:26:47 +13:00
Rob Bygrave 9aee7c892d Test only - copy/add multi-tenant partition tests 2018-02-23 10:14:27 +13:00
Rob Bygrave 18d944f228 Test only - copy/add multi-tenant partition tests 2018-02-23 10:13:57 +13:00
Rob Bygrave 29bb9d3b48 #1268 - SQL Server fix for "for update" plus Sequences support plus refactor of sequences 2018-02-22 22:46:17 +13:00
Rob Bygrave f8ea5619d0 #1268 - SQL Server fix for "for update" plus Sequences support plus refactor of sequences 2018-02-22 22:45:44 +13:00
Rob Bygrave e3fac7c622 #1269 - Paging queries should not ALWAYS append an order by ID ... but only when the order by is empty 2018-02-22 22:43:14 +13:00
Rob Bygrave 8ee68ac5e1 #1269 - Paging queries should not ALWAYS append an order by ID ... but only when the order by is empty 2018-02-22 22:40:22 +13:00
Rob Bygrave ec7b376612 Test only change - fix test assert for sql server 2018-02-22 22:12:30 +13:00
Rob Bygrave f2d438f21a Test only change - fix test assert for sql server 2018-02-22 22:11:56 +13:00
Rob Bygrave 20bd2241e4 Test only change - fix test assert to support sequence and identity insert 2018-02-22 22:11:36 +13:00
Rob Bygrave dd907cc4bc Test only change - fix keys for testing against Oracle 2018-02-22 22:10:59 +13:00
Rob Bygrave 4d122ea05b #1267 - DDL issue on @OneToMany @JoinTable ... extra unnecessary index created on fk column 2018-02-22 22:09:53 +13:00
Rob Bygrave 83384c6f0e #323 - DDL Generation support for foreign key cascade options
Tidy up, no GLOBAL_DEFAULT.
2018-02-21 11:04:04 +13:00
Rob Bygrave eaa48468cd #1174 - ENH: Support mapping @TenantId to an entity bean via @ManyToOne 2018-02-21 09:44:37 +13:00
Rob Bygrave 0b1c3a4233 Bump version to 11.11.1-SNAPSHOT 2018-02-21 09:44:07 +13:00
Rob Bygrave 3e8b06e38e #994 - @OneToOne cacade does not honor orphanRemoval = true 2018-02-20 16:59:55 +13:00
Rob Bygrave 81d48d67a7 #123 - Mapping - Add support for OneToMany JoinTables 2018-02-20 14:40:21 +13:00
Rob Bygrave 8d0df6541e Merge branch 'issue/1263-transaction-api' 2018-02-19 22:47:42 +13:00
Rob Bygrave 2be3099d55 #1228 - Add support to not generate FK Constraint when @JoinColumn(foreignKey=@ForeignKey(ConstraintMode.NO_CONSTRAINT)) 2018-02-19 22:45:47 +13:00
Rob Bygrave 183d079b58 #323 - DDL Generation support for foreign key cascade options 2018-02-19 22:24:51 +13:00
Rob Bygrave 40634412f5 #323 - DDL Generation support for foreign key cascade options 2018-02-19 22:24:37 +13:00
Roland PramlandRob Bygrave b8aa822a30 [utf8mb4-support] limited the size of all indexed string columns, as max key length on mysql/mariadb is 767 bytes (which are <= 191 chars on utf8mb4) (#1264) 2018-02-19 16:15:28 +13:00
Rob Bygrave a1d0fbb194 [maven-release-plugin] prepare for next development iteration 2018-02-16 14:45:29 +13:00
Rob Bygrave 70ccc6588d [maven-release-plugin] prepare release ebean-11.10.6 2018-02-16 14:45:16 +13:00
Rob Bygrave 3d62e84d59 #1263 - ENH: Make Transaction API Fluid such that we can set multiple thing like batchSize and label in a fluid fashion 2018-02-16 14:40:35 +13:00
Rob Bygrave ac36172cc6 #997 - TypeReflectHelper does not resolve type variables correctly in non-trivial cases
Part 2 - Fix the failing test, swap in use of TypeResolver and remove all the unused features.
2018-02-16 12:47:10 +13:00
Rob Bygrave 1829c74694 #997 - TypeReflectHelper does not resolve type variables correctly in non-trivial cases
Initial failing test ... and TypeResolver prior to cleanup
2018-02-16 12:25:07 +13:00
Rob Bygrave cc7e22014d #997 - TypeReflectHelper does not resolve type variables correctly in non-trivial cases
Initial failing test ... and TypeResolver prior to cleanup
2018-02-16 12:24:57 +13:00
Rob Bygrave a5c6f483b0 #1258 - ENH: Add ServerConfig notifyL2CacheInForeground option ... was ServerCachePlugin BackgroundExecutor problem 2018-02-16 11:18:17 +13:00
Rob Bygrave 5237e8ba66 #1261 - ENH: Support use of getGeneratedKeys with SqlUpdate 2018-02-16 09:45:49 +13:00
Rob Bygrave 9926cad7f1 #1262 - ENH: Support firstRow maxRows with findNative() 2018-02-16 00:11:27 +13:00
Rob Bygrave 6c1b295d41 [maven-release-plugin] prepare for next development iteration 2018-02-15 22:32:56 +13:00
Rob Bygrave 49b568e7d4 [maven-release-plugin] prepare release ebean-11.10.5 2018-02-15 22:32:43 +13:00
Rob Bygrave 628af31ddd #1142 - Mapping - Add support for @OrderColumn (Ebean automatically maintains an order column) 2018-02-15 22:31:41 +13:00
Rob Bygrave 31cac60393 #107 - Mapping - Add support for @PrimaryKeyJoinColumn/@PrimaryKeyJoinColumns
Adds Bi-directional support
2018-02-15 00:49:51 +13:00
Rob Bygrave 6a067c0cf7 #107 - Mapping - Add support for @PrimaryKeyJoinColumn/@PrimaryKeyJoinColumns
Extra test with UUID and optional OneToOne so has left join
2018-02-14 23:09:10 +13:00
Rob Bygrave 842292ef6b #107 - Mapping - Add support for @PrimaryKeyJoinColumn/@PrimaryKeyJoinColumns 2018-02-14 22:59:57 +13:00
Rob Bygrave 6db9a60f4f #107 - Fix existing test - Ah ??? ... this PrimaryKeyJoinColumn was ignored and should never have been in this test. 2018-02-14 17:24:14 +13:00
Rob Bygrave d9125db957 [maven-release-plugin] prepare for next development iteration 2018-02-14 15:29:26 +13:00
Rob Bygrave 4bd9e67703 [maven-release-plugin] prepare release ebean-11.10.4 2018-02-14 15:29:12 +13:00
Rob Bygrave 2a986ab590 #1260 - Mark query.setForUpdate(true) as deprecated - migrate to query.forUpdate() 2018-02-14 15:28:13 +13:00
Rob Bygrave 7c477827fe #1259 - Throw explicit mapping error when an Enum is mapped to both ORDINAL and STRING 2018-02-14 12:39:35 +13:00
Rob Bygrave b2967dec1e #1257 - ServerConfig with register = true, defaultServer = false ... fails as it tries to create the default server 2018-02-13 16:22:52 +13:00
Rob Bygrave a95fd063ae #1256 - Query where many isNotEmpty() ... on ManyToMany with SoftDelete produces incorrect SQL (Part 2) 2018-02-13 14:52:18 +13:00
Rob Bygrave 17a54e3804 No effective change - extra tests for DeployPropertyParser 2018-02-13 14:09:08 +13:00
Rob Bygrave f1b2a09b30 Testing - modify .travis to run mysql as service (bump MySql version to 5.7) 2018-02-10 01:16:40 +13:00
Rob Bygrave e4d9e4f2de Testing - modify .travis to run mysql as service (bump MySql version to 5.7) 2018-02-10 01:04:19 +13:00
Rob Bygrave 6474f306b5 #1255 - Regression on raw LIKE expression with slashes on Postgres
Related update for Sqlite
2018-02-10 00:57:19 +13:00
Rob Bygrave f1c2f134ad #1255 - Regression on raw LIKE expression with slashes on Postgres
Related update for Sqlite
2018-02-10 00:56:05 +13:00
Rob Bygrave 0f7158ea18 Testing - modify .travis to run mysql as service 2018-02-10 00:34:34 +13:00
Rob Bygrave 3fdb76d18f Testing - modify .travis to run mysql as service 2018-02-10 00:31:39 +13:00
Rob Bygrave b1027a2f25 Testing - modify .travis to run mysql as docker container 2018-02-10 00:18:33 +13:00
Rob Bygrave e5a1f9f7cc Testing - modify .travis adding docker service 2018-02-10 00:07:27 +13:00
Rob Bygrave f78f24136a [maven-release-plugin] prepare for next development iteration 2018-02-09 23:42:42 +13:00
Rob Bygrave 5b2aa0c74d [maven-release-plugin] prepare release ebean-11.10.3 2018-02-09 23:42:28 +13:00
Rob Bygrave 175714046a #1254 - SpringJdbcTransactionManager inifinite recursion #1254
Yeah, nah. This is the real fix. Expose:

   SpiTransaction TransctionManager.getInScope();

... and have ebean-spring-txn SpringJdbcTransactionManager call it instead of getActive().
2018-02-09 23:03:07 +13:00
Rob Bygrave 73b2cc55f3 #1254 - SpringJdbcTransactionManager inifinite recursion #1254
Fix. Note tested in ebean-spring-txn.
2018-02-09 22:22:08 +13:00
Rob Bygrave cebe375856 No effective change - initial docker testing setup for oracle and sqlserver 2018-02-09 21:31:40 +13:00
Rob Bygrave 2f47235018 #1255 - Regression on raw LIKE expression with slashes on Postgres 2018-02-09 21:28:39 +13:00
Rob Bygrave 17abd6d977 #1255 - Regression on raw LIKE expression with slashes on Postgres
Tests
2018-02-09 21:25:36 +13:00
Rob Bygrave cc5f3689f3 No effective change - modify tests for SqlServer 2018-02-09 21:10:18 +13:00
Rob Bygrave abcc651e7f #1253 - Query where many isNotEmpty() ... on ManyToMany with SoftDelete produces incorrect SQL
Fix
2018-02-09 15:00:58 +13:00
Rob Bygrave caa36262c1 #1253 - Query where many isNotEmpty() ... on ManyToMany with SoftDelete produces incorrect SQL
Failing test
2018-02-09 15:00:29 +13:00
Rob Bygrave 66b6dba921 [maven-release-plugin] prepare for next development iteration 2018-02-07 16:54:44 +13:00
Rob Bygrave fc01f230f3 [maven-release-plugin] prepare release ebean-11.10.2 2018-02-07 16:54:31 +13:00
Rob Bygrave 7fe3e657d7 No effective change - put default test db back to default to h2 2018-02-07 16:49:49 +13:00
Rob Bygrave 9bade12c9d #1251 - Delete cascade with 11+ and Postgres throws exception
Fix
2018-02-07 16:47:59 +13:00
Rob Bygrave 7a816ae2da #1251 - Delete cascade with 11+ and Postgres throws exception
Failing test
2018-02-07 16:40:12 +13:00
Rob Bygrave b83412281e #1251 - Delete cascade with 11+ and Postgres throws exception
Failing test
2018-02-07 16:39:58 +13:00
Rob Bygrave 478bba292d #1250 - Add explicit coverage for Enum with abstract method - no new issue 2018-02-07 14:01:11 +13:00
Rob Bygrave 77a4e684bb [maven-release-plugin] prepare for next development iteration 2018-02-02 23:39:51 +13:00
Rob Bygrave a8254c4295 [maven-release-plugin] prepare release ebean-11.10.1 2018-02-02 23:39:38 +13:00
Rob Bygrave 2a5d0084ef #1249 - Bump db-migration to 11.3.1 ... with fix for not running empty sql statements (empty strings) 2018-02-02 23:38:11 +13:00
Rob Bygrave f9404639d7 Fix test for H2 select for update - my bad. 2018-02-02 23:36:12 +13:00
Rob Bygrave bbef5a6bbb #1243 - Add IDENTITY support for Oracle platform (since 12c Oracle supports auto generated identity) 2018-02-02 23:18:23 +13:00
Rob Bygrave 4645fd9725 Cleanup tests for Oracle 2018-02-02 23:17:21 +13:00
Rob Bygrave 823cb45221 Cleanup tests for Oracle 2018-02-02 23:17:03 +13:00
Rob Bygrave b2bbb05b83 #1248 - ENH: Additionally read "db" system property to determine default database (to use when testing with docker against different databases) 2018-02-02 20:49:39 +13:00
Rob Bygrave 5ff71324d0 #1247 - Unique index or primary key violation - when jdbc batch cascading to PrivateOwned child with unique constraint 2018-02-02 20:43:55 +13:00
Rob Bygrave 851f1c98b0 #1245 - Refactor internals - move Transaction ProfileStream creation
Stops the profileId parameter pollution, enables to potentially use transaction label
and runtime toggling of collecting transaction profiling detail.
2018-01-31 14:02:50 +13:00
Rob Bygrave a92e620f7e #1245 - Refactor internals - move logic to TransactionManager from DefaultServer 2018-01-31 13:36:23 +13:00
Rob Bygrave f9cd955f7a #1245 - Refactor internals - Add to TransactionManager externalRemoveTransaction() and externalBeginTransaction()
These provide a better/tidier way for external transaction managers (like ebean-spring-txn) to begin and end externally
managed transactions.
2018-01-31 13:28:42 +13:00
Rob Bygrave 71f66005e7 Merge branch 'master' of github.com:ebean-orm/ebean 2018-01-31 11:57:10 +13:00
Rob Bygrave b682293a32 #1245 - Refactor internals - move logic to TransactionManager from DefaultServer 2018-01-31 11:56:38 +13:00
Rob Bygrave 5bd7853ce8 #1245 - Refactor internals - move TransactionMangerScope to be part of TransactionManager 2018-01-31 11:30:11 +13:00
André CamiloandRob Bygrave 276c56161a DiscriminatorColumn length should be 31 (#1244) 2018-01-31 08:32:21 +13:00
Rob Bygrave 6b01821623 #1242 - Bump avaje-classpath-scanner dependency to 3.1.1 2018-01-26 11:51:28 +13:00
Rob Bygrave 8495ccced6 #1241 - ENH: Support using ebean_db system property to control docker container for testing 2018-01-24 19:17:14 +13:00
Rob Bygrave 2d929837c4 No effective change - Update testing for profile location enhancement (#1227) 2018-01-24 19:11:59 +13:00
Rob Bygrave 6754992b1c [maven-release-plugin] prepare for next development iteration 2018-01-22 23:02:52 +13:00
477 changed files with 11657 additions and 2857 deletions
+11
View File
@@ -9,16 +9,27 @@ git:
addons:
postgresql: "9.6"
# apt:
# packages:
# - mysql-server-5.7
# - mysql-client-core-5.7
# - mysql-client-5.7
services:
- docker
- postgresql
- mysql
before_script:
- ./.travis/setup_database
- mysql -u root -e 'CREATE DATABASE test_ebean;'
- mysql -u root -e "CREATE USER 'test_ebean'@'localhost' IDENTIFIED BY 'test';"
- mysql -u root -e "GRANT ALL ON test_ebean.* TO 'test_ebean'@'localhost';"
env:
- EBEAN_DB=h2
- EBEAN_DB=pg
# - EBEAN_DB=mysql
install: true
+7 -6
View File
@@ -48,7 +48,8 @@ Reqirements
run: `mvn clean test -Ddatasource.default=pg`
Current status: PASS
Tests run: 1986, Failures: 0, Errors: 0, Skipped: 16
Tests run: 2166, Failures: 0, Errors: 0, Skipped: 25
'mysql' platform
@@ -61,8 +62,8 @@ Reqirements
run: `mvn clean test -Ddatasource.default=mysql`
Current status: FAIL
Tests run: 1986, Failures: 2, Errors: 4, Skipped: 16
Current status: PASS
Tests run: 2166, Failures: 0, Errors: 0, Skipped: 33
@@ -76,8 +77,8 @@ Reqires an installed sqlserver - e.g. https://hub.docker.com/r/microsoft/mssql-s
run: `mvn clean test -Ddatasource.default=mssql`
Current status: FAIL
Tests run: 1986, Failures: 19, Errors: 43, Skipped: 16
Current status: PASS
Tests run: 2166, Failures: 0, Errors: 0, Skipped: 39
@@ -107,7 +108,7 @@ Reqires a locally installed DB2 Express-C server.
run: `mvn clean test -Ddatasource.default=db2 -Pdb2`
Current status: FAIL
Tests run: 1986, Failures: 33, Errors: 26, Skipped: 16
Tests run: 2166, Failures: 29, Errors: 58, Skipped: 30
'sqlite' platform
+27 -19
View File
@@ -9,7 +9,7 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.9.1</version>
<version>11.12.1</version>
<packaging>jar</packaging>
<name>ebean</name>
@@ -17,7 +17,7 @@
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-11.9.1</tag>
<tag>ebean-11.12.1</tag>
</scm>
<profiles>
@@ -83,6 +83,15 @@
<scope>provided</scope>
</dependency>
<!--
Exclude snakeyaml if you desire - it is not strictly required.
-->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.19</version>
</dependency>
<!--
Class retention Nonnull and Nullable annotations
to assist with IDE auto-completion with Ebean API
@@ -103,7 +112,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-annotation</artifactId>
<version>3.6</version>
<version>3.8</version>
</dependency>
<dependency>
@@ -112,22 +121,16 @@
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-classpath-scanner-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-classpath-scanner</artifactId>
<version>2.2.5</version>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-migration</artifactId>
<version>11.2.1</version>
<version>11.3.1</version>
</dependency>
<dependency>
@@ -197,12 +200,12 @@
<!--<scope>test</scope>-->
<!--</dependency>-->
<!--<dependency>-->
<!--<groupId>microsoft</groupId>-->
<!--<artifactId>sqlserver-jdbc</artifactId>-->
<!--<version>4.2</version>-->
<!--<scope>test</scope>-->
<!--</dependency>-->
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-docker-run</artifactId>
<version>1.5.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
@@ -221,7 +224,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-agent</artifactId>
<version>11.5.1</version>
<version>11.10.1</version>
<scope>test</scope>
</dependency>
@@ -294,7 +297,7 @@
<plugin>
<groupId>io.ebean</groupId>
<artifactId>ebean-maven-plugin</artifactId>
<version>11.5.1</version>
<version>11.10.1</version>
<executions>
<execution>
<id>test</id>
@@ -327,6 +330,11 @@
<name>datasource.default</name>
<value>${datasource.default}</value>
</property>
<property>
<!-- transfer dbClockDelta parameter -->
<name>dbClockDelta</name>
<value>${dbClockDelta}</value>
</property>
</systemProperties>
</configuration>
</plugin>
+169
View File
@@ -0,0 +1,169 @@
package io.ebean;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Optional;
/**
* Provides finder functionality for use with "Dependency Injection style" use of Ebean.
* <p>
* Note that typically users would extend BeanRepository rather than BeanFinder.
* </p>
* <pre>{@code
*
* public class CustomerFinder extends BeanFinder<Long,Customer> {
*
* @Inject
* public CustomerFinder(EbeanServer server) {
* super(Customer.class, server);
* }
*
* // ... add customer specific finders
* }
*
* }</pre>
*
* @param <I> The ID type
* @param <T> The Bean type
*/
public abstract class BeanFinder<I,T> {
protected final EbeanServer server;
protected final Class<T> type;
/**
* Create with the given bean type and EbeanServer instance.
*
* @param type The bean type
* @param server The EbeanServer instance typically created via Spring factory or equivalent.
*/
protected BeanFinder(Class<T> type, EbeanServer server) {
this.type = type;
this.server = server;
}
/**
* Return the EbeanServer to use.
*/
public EbeanServer db() {
return server;
}
/**
* Return the current transaction.
*/
public Transaction currentTransaction() {
return db().currentTransaction();
}
/**
* Flush the JDBC batch on the current transaction.
*/
public void flush() {
db().flush();
}
/**
* Return typically a different EbeanServer to the default.
* <p>
* This is equivalent to {@link Ebean#getServer(String)}
*
* @param server The name of the EbeanServer. If this is null then the default EbeanServer is
* returned.
*/
public EbeanServer db(String server) {
return Ebean.getServer(server);
}
/**
* Creates an entity reference for this ID.
* <p>
* Equivalent to {@link EbeanServer#getReference(Class, Object)}
*/
@Nonnull
public T ref(I id) {
return db().getReference(type, id);
}
/**
* Retrieves an entity by ID.
* <p>
* Equivalent to {@link EbeanServer#find(Class, Object)}
*/
@Nullable
public T findById(I id) {
return db().find(type, id);
}
/**
* Find an entity by ID returning an Optional.
*/
@Nullable
public Optional<T> findByIdOrEmpty(I id) {
return db().find(type).setId(id).findOneOrEmpty();
}
/**
* Delete a bean by Id.
* <p>
* Equivalent to {@link EbeanServer#delete(Class, Object)}
*/
public void deleteById(I id) {
db().delete(type, id);
}
/**
* Retrieves all entities of the given type.
*/
@Nonnull
public List<T> findAll() {
return query().findList();
}
/**
* Creates an update query.
*
* <pre>{@code
*
* int rows =
* updateQuery()
* .set("status", Customer.Status.ACTIVE)
* .set("updtime", new Timestamp(System.currentTimeMillis()))
* .where()
* .gt("id", 1000)
* .update();
*
* }</pre>
*
* <p>
* Equivalent to {@link EbeanServer#update(Class)}
*/
protected UpdateQuery<T> updateQuery() {
return db().update(type);
}
/**
* Creates a query.
* <p>
* Equivalent to {@link EbeanServer#find(Class)}
*/
protected Query<T> query() {
return db().find(type);
}
/**
* Creates a native sql query.
*/
protected Query<T> nativeSql(String nativeSql) {
return db().findNative(type, nativeSql);
}
/**
* Creates a query using the ORM query language.
*/
protected Query<T> query(String ormQuery) {
return db().createQuery(type, ormQuery);
}
}
+178
View File
@@ -0,0 +1,178 @@
package io.ebean;
import io.ebean.bean.EntityBean;
/**
* Provides finder functionality for use with "Dependency Injection style" use of Ebean.
* <p>
* <pre>{@code
*
* @Repository
* public class CustomerRepository extends BeanRepository<Long,Customer> {
*
* @Inject
* public CustomerRepository(EbeanServer server) {
* super(Customer.class, server);
* }
*
* // ... add customer specific finders and persist logic
*
* public List<Customer> findByName(String nameStart) {
* return query().where()
* .istartsWith("name", nameStart)
* .findList();
* }
*
* }
* }</pre>
*
* @param <I> The ID type
* @param <T> The Bean type
*/
public abstract class BeanRepository<I, T> extends BeanFinder<I, T> {
/**
* Create with the given bean type and EbeanServer instance.
* <p>
* Typically users would extend BeanRepository rather than BeanFinder.
* </p>
* <pre>{@code
*
* @Inject
* public CustomerRepository(EbeanServer server) {
* super(Customer.class, server);
* }
*
* }</pre>
*
* @param type The bean type
* @param server The EbeanServer instance typically created via Spring factory or equivalent
*/
protected BeanRepository(Class<T> type, EbeanServer server) {
super(type, server);
}
/**
* Marks the entity bean as dirty.
* <p>
* This is used so that when a bean that is otherwise unmodified is updated the version
* property is updated.
* <p>
* An unmodified bean that is saved or updated is normally skipped and this marks the bean as
* dirty so that it is not skipped.
* <p>
* <pre>{@code
*
* Customer customer = customerRepository.byId(id);
*
* // mark the bean as dirty so that a save() or update() will
* // increment the version property
*
* customerRepository.markAsDirty(customer);
* customerRepository.save(customer);
*
* }</pre>
*
* @see EbeanServer#markAsDirty(Object)
*/
public void markAsDirty(T bean) {
db().markAsDirty(bean);
}
/**
* Mark the property as unset or 'not loaded'.
* <p>
* This would be used to specify a property that we did not wish to include in a stateless update.
* </p>
* <pre>{@code
*
* // populate an entity bean from JSON or whatever
* Customer customer = ...;
*
* // mark the email property as 'unset' so that it is not
* // included in a 'stateless update'
* customerRepository.markPropertyUnset(customer, "email");
*
* customerRepository.update(customer);
*
* }</pre>
*
* @param propertyName the name of the property on the bean to be marked as 'unset'
*/
public void markPropertyUnset(T bean, String propertyName) {
((EntityBean) bean)._ebean_getIntercept().setPropertyLoaded(propertyName, false);
}
/**
* Insert or update this entity depending on its state.
* <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#save(Object)
*/
public void save(T bean) {
db().save(bean);
}
/**
* Update this entity.
*
* @see EbeanServer#update(Object)
*/
public void update(T bean) {
db().update(bean);
}
/**
* Insert this entity.
*
* @see EbeanServer#insert(Object)
*/
public void insert(T bean) {
db().insert(bean);
}
/**
* Delete this bean.
* <p>
* This will return true if the bean was deleted successfully or JDBC batch is being used.
* </p>
* <p>
* If there is no current transaction one will be created and committed for
* you automatically.
* </p>
* <p>
* If the Bean does not have a version property (or loaded version property) and
* the bean does not exist then this returns false indicating that nothing was
* deleted. Note that, if JDBC batch mode is used then this always returns true.
* </p>
*
* @see EbeanServer#delete(Object)
*/
public boolean delete(T bean) {
return db().delete(bean);
}
/**
* Delete a bean permanently without soft delete.
* <p>
* This is used when the bean contains a <code>@SoftDelete</code> property and we
* want to perform a hard/permanent delete.
* </p>
*
* @see EbeanServer#deletePermanent(Object)
*/
public boolean deletePermanent(T bean) {
return db().deletePermanent(bean);
}
/**
* Refreshes this entity from the database.
*
* @see EbeanServer#refresh(Object)
*/
public void refresh(T bean) {
db().refresh(bean);
}
}
+7 -12
View File
@@ -5,10 +5,10 @@ import io.ebean.cache.ServerCacheManager;
import io.ebean.config.ServerConfig;
import io.ebean.text.csv.CsvReader;
import io.ebean.text.json.JsonContext;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import java.util.Collection;
@@ -113,7 +113,10 @@ import java.util.concurrent.ConcurrentHashMap;
*/
public final class Ebean {
private static final Logger logger = LoggerFactory.getLogger(Ebean.class);
static {
EbeanVersion.getVersion(); // initalizes the version class and logs the version.
}
/**
* Manages creation and cache of EbeanServers.
*/
@@ -131,8 +134,7 @@ public final class Ebean {
private final ConcurrentHashMap<String, EbeanServer> concMap = new ConcurrentHashMap<>();
/**
* Cache for synchronized read, creation and put. Protected by the monitor
* object.
* Cache for synchronized read, creation and put. Protected by the monitor object.
*/
private final HashMap<String, EbeanServer> syncMap = new HashMap<>();
@@ -146,14 +148,7 @@ public final class Ebean {
private ServerManager() {
try {
// skipDefaultServer is set by EbeanServerFactory
// ... when it is creating the primaryServer
if (PrimaryServer.isSkip()) {
// primary server being created by EbeanServerFactory
// ... so we should not try and create it here
logger.debug("PrimaryServer.isSkip()");
} else {
if (!PrimaryServer.isSkip()) {
// look to see if there is a default server defined
String defaultName = PrimaryServer.getDefaultServerName();
logger.debug("defaultName:{}", defaultName);
@@ -33,6 +33,10 @@ public class EbeanServerFactory {
private static SpiContainer container;
static {
EbeanVersion.getVersion(); // initalizes the version class and logs the version.
}
/**
* Initialise the container with clustering configuration.
* <p>
@@ -65,10 +69,8 @@ public class EbeanServerFactory {
EbeanServer server = createInternal(config);
if (config.isDefaultServer()) {
PrimaryServer.setSkip(true);
}
if (config.isRegister()) {
PrimaryServer.setSkip(true);
Ebean.register(server, config.isDefaultServer());
}
+43
View File
@@ -0,0 +1,43 @@
package io.ebean;
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
*
*/
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");
}
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;
}
}
+2
View File
@@ -158,6 +158,7 @@ public class Pairs {
return this;
}
@Override
public String toString() {
return "p0:" + property0 + " p1:" + property1 + " entries:" + entries;
}
@@ -183,6 +184,7 @@ public class Pairs {
this.b = b;
}
@Override
public String toString() {
return "{" + a + "," + b + "}";
}
+5 -2
View File
@@ -1,6 +1,6 @@
package io.ebean;
import io.ebean.config.PropertyMap;
import io.ebean.config.properties.PropertiesLoader;
import java.util.Properties;
@@ -44,7 +44,7 @@ class PrimaryServer {
*/
static synchronized Properties getProperties() {
if (globalProperties == null) {
globalProperties = PropertyMap.defaultProperties();
globalProperties = PropertiesLoader.load();
}
if (defaultServerName == null) {
defaultServerName = determineDefaultServerName();
@@ -59,6 +59,8 @@ class PrimaryServer {
private static String determineDefaultServerName() {
String defaultServerName = System.getenv("EBEAN_DB");
defaultServerName = System.getProperty("db", defaultServerName);
defaultServerName = System.getProperty("ebean_db", defaultServerName);
if (isEmpty(defaultServerName)) {
defaultServerName = System.getProperty("datasource.default");
if (isEmpty(defaultServerName)) {
@@ -74,6 +76,7 @@ class PrimaryServer {
if (defaultServerName == null) {
defaultServerName = "db";
}
System.setProperty("ebean_db", defaultServerName);
return defaultServerName;
}
+16
View File
@@ -1436,9 +1436,12 @@ 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);
/**
@@ -1482,6 +1485,11 @@ public interface Query<T> {
*/
Class<T> getBeanType();
/**
* Return the type of query being executed.
*/
QueryType getQueryType();
/**
* Set true if you want to disable lazy loading.
* <p>
@@ -1499,4 +1507,12 @@ public interface Query<T> {
*/
Set<String> validate();
/**
* Controls, if paginated queries should always append an 'order by id' statement at the end to
* guarantee a deterministic sort result. This may affect performance.
* If this is not enabled, and an orderBy is set on the query, it's up to the programmer that
* this query provides a deterministic result.
*/
Query<T> orderById(boolean orderById);
}
+22
View File
@@ -0,0 +1,22 @@
package io.ebean;
/**
* The type of the query being executed.
*/
public enum QueryType {
/**
* A find query.
*/
FIND,
/**
* An update query.
*/
UPDATE,
/**
* A delete query.
*/
DELETE
}
+24
View File
@@ -54,6 +54,25 @@ public interface SqlUpdate {
*/
int execute();
/**
* Return the generated key value.
*/
Object getGeneratedKey();
/**
* Execute and return the generated key. This is effectively a short cut for:
*
* <pre>{@code
*
* sqlUpdate.execute();
* Object key = sqlUpdate.getGeneratedKey();
*
* }</pre>
*
* @return The generated key value
*/
Object executeGetKey();
/**
* Return true if eBean should automatically deduce the table modification
* information and process it.
@@ -88,6 +107,11 @@ public interface SqlUpdate {
*/
SqlUpdate setLabel(String label);
/**
* Set to true when we want to use getGeneratedKeys with this statement.
*/
SqlUpdate setGetGeneratedKeys(boolean getGeneratedKeys);
/**
* Return the sql statement.
*/
@@ -191,6 +191,11 @@ public interface BeanCollection<E> extends Serializable {
*/
void setModifyListening(ModifyListenMode modifyListenMode);
/**
* Return the current modify listening mode. Can be null for on newly created beans.
*/
ModifyListenMode getModifyListening();
/**
* Add an object to the additions list.
* <p>
@@ -227,6 +232,11 @@ public interface BeanCollection<E> extends Serializable {
*/
void modifyReset();
/**
* Has been modified by an addition or removal.
*/
boolean wasTouched();
/**
* Return a shallow copy of this collection that is modifiable.
*/
@@ -43,6 +43,7 @@ public final class CallStack implements Serializable {
return hc;
}
@Override
public int hashCode() {
return hc;
}
@@ -90,6 +90,7 @@ public final class EntityBeanIntercept implements Serializable {
private int lazyLoadProperty = -1;
private Object ownerId;
private int sortOrder;
/**
* Create a intercept with a given entity.
@@ -693,6 +694,9 @@ public final class EntityBeanIntercept implements Serializable {
* Add and return a dirty property hash recursing into embedded beans.
*/
private void addDirtyPropertyKey(StringBuilder sb) {
if (sortOrder > 0) {
sb.append("s,");
}
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
if (changedProps != null && changedProps[i]) {
@@ -1050,4 +1054,18 @@ public final class EntityBeanIntercept implements Serializable {
public void setOldValue(int propertyIndex, Object oldValue) {
setChangedPropertyValue(propertyIndex, true, oldValue);
}
/**
* Return the sort order value for an order column.
*/
public int getSortOrder() {
return sortOrder;
}
/**
* Set the sort order value for an order column.
*/
public void setSortOrder(int sortOrder) {
this.sortOrder = sortOrder;
}
}
@@ -140,6 +140,11 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
// Support for modify additions deletions etc - ManyToMany
// ---------------------------------------------------------
@Override
public ModifyListenMode getModifyListening() {
return modifyListenMode;
}
/**
* set modifyListening to be on or off.
*/
@@ -213,7 +218,12 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
boolean holdsModifications() {
return modifyHolder != null && modifyHolder.hasModifications();
}
@Override
public boolean wasTouched() {
return modifyHolder != null && modifyHolder.wasTouched();
}
/**
* Copies all relevant properties for a clone. See {@link #getShallowCopy()}
* @param other
@@ -28,7 +28,10 @@ class ModifyHolder<E> implements Serializable {
*/
private Set<E> modifyAdditions = new LinkedHashSet<>();
private boolean touched;
void reset() {
touched = false;
modifyDeletions = new LinkedHashSet<>();
modifyAdditions = new LinkedHashSet<>();
}
@@ -50,6 +53,7 @@ class ModifyHolder<E> implements Serializable {
void modifyAddition(E bean) {
if (bean != null) {
touched = true;
// If it is to delete then just remove the deletion
if (!undoDeletion(bean)) {
// Insert
@@ -65,6 +69,7 @@ class ModifyHolder<E> implements Serializable {
@SuppressWarnings("unchecked")
void modifyRemoval(Object bean) {
if (bean != null) {
touched = true;
// If it is to be added then just remove the addition
if (!undoAddition(bean)) {
modifyDeletions.add((E) bean);
@@ -80,6 +85,14 @@ class ModifyHolder<E> implements Serializable {
return modifyDeletions;
}
/**
* Return true if the collection was touched in some way. This is still true even if
* a bean was removed and then added (which is not held as a modification).
*/
boolean wasTouched() {
return touched;
}
/**
* Return true if there additions or removals.
*/
@@ -0,0 +1,15 @@
package io.ebean.config;
/**
* Used to provide some automatic configuration early in the creation of an EbeanServer.
*/
public interface AutoConfigure {
/**
* Provide some configuration the ServerConfig prior to server creation.
* <p>
* Return true if the autoConfiguration applies to this ServerConfig.
*/
void configure(ServerConfig serverConfig);
}
@@ -1,11 +1,16 @@
package io.ebean.config;
import io.ebean.EbeanVersion;
import io.ebean.annotation.Platform;
import io.ebean.migration.MigrationConfig;
import io.ebean.migration.MigrationRunner;
import io.ebean.util.StringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.Properties;
@@ -116,6 +121,11 @@ public class DbMigrationConfig {
*/
protected boolean strictMode = true;
/**
* Contains the DDL-header information.
*/
protected String ddlHeader;
/**
* Return the DB platform to generate migration DDL for.
* <p>
@@ -387,6 +397,14 @@ public class DbMigrationConfig {
return patchResetChecksumOn;
}
/**
* Returns a DDL header prepend for each DDL. E.g. for copyright headers
* You can use placeholders like ${version} or ${timestamp} in properties file.
*/
public String getDdlHeader() {
return ddlHeader;
}
/**
* Set migration versions that should have their checksum reset and not run.
* <p>
@@ -454,6 +472,11 @@ public class DbMigrationConfig {
String adminPwd = properties.get("datasource." + serverName + ".password", dbPassword);
adminPwd = properties.get("datasource." + serverName + ".adminpassword", adminPwd);
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 ));
}
}
/**
@@ -10,16 +10,13 @@ public class PropertiesWrapper {
protected final String serverName;
protected final PropertyMap propertyMap;
/**
* Construct with a prefix, serverName and properties.
*/
public PropertiesWrapper(String prefix, String serverName, Properties properties) {
this.serverName = serverName;
this.prefix = prefix;
this.propertyMap = PropertyMapLoader.load(null, properties);
this.properties = propertyMap.asProperties();
this.properties = properties;
}
/**
@@ -29,27 +26,6 @@ public class PropertiesWrapper {
this(null, null, properties);
}
/**
* Internal copy constructor when changing prefix.
*/
protected PropertiesWrapper(String prefix, String serverName, PropertyMap propertyMap, Properties properties) {
this.serverName = serverName;
this.prefix = prefix;
this.propertyMap = propertyMap;
this.properties = properties;
}
/**
* Return a PropertiesWrapper instance with a different prefix but same underlying properties.
* <p/>
* Used when wanting to use "datasource" as the prefix rather than "ebean".
* <p/>
* The returning instance should only be used in a read only fashion.
*/
public PropertiesWrapper withPrefix(String prefix) {
return new PropertiesWrapper(prefix, serverName, propertyMap, properties);
}
/**
* Return the serverName (optional).
*/
@@ -57,16 +33,6 @@ public class PropertiesWrapper {
return serverName;
}
/**
* Return as Properties with lower case keys and after evaluation and additional properties loading has occurred.
* <p>
* Ebean has historically ignored the case of keys hence returning the Properties with all the keys lower cased.
* </p>
*/
public Properties asPropertiesLowerCase() {
return properties;
}
/**
* Get a property with no default value.
*/
@@ -90,17 +56,21 @@ public class PropertiesWrapper {
String value = null;
if (serverName != null && prefix != null) {
value = propertyMap.get(prefix + "." + serverName + "." + key, null);
value = internalGet(prefix + "." + serverName + "." + key);
}
if (value == null && prefix != null) {
value = propertyMap.get(prefix + "." + key, null);
value = internalGet(prefix + "." + key);
}
if (value == null) {
value = propertyMap.get(key, null);
value = internalGet(key);
}
return value == null ? defaultValue : value;
}
private String internalGet(String key) {
return properties.getProperty(key);
}
/**
* Return a double property value.
*/
@@ -1,178 +0,0 @@
package io.ebean.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.naming.InitialContext;
import javax.naming.NamingException;
/**
* Helper used to evaluate expressions such as ${CATALINA_HOME}.
* <p>
* The expressions can contain environment variables, system properties or JNDI
* properties. JNDI expressions take the form ${jndi:propertyName} where you
* substitute propertyName with the name of the jndi property you wish to
* evaluate.
* </p>
*/
final class PropertyExpression {
private static final Logger logger = LoggerFactory.getLogger(PropertyExpression.class);
/**
* Prefix for looking up JNDI Environment variable.
*/
private static final String JAVA_COMP_ENV = "java:comp/env/";
/**
* Used to detect the start of an expression.
*/
private static final String START = "${";
/**
* Used to detect the end of an expression.
*/
private static final String END = "}";
/**
* Specify the PropertyHolder.
*/
private PropertyExpression() {
}
/**
* Return the property value evaluating and replacing any expressions such as
* ${CATALINA_HOME}.
*/
static String eval(String val, PropertyMap map) {
if (val == null) {
return null;
}
int sp = val.indexOf(START);
if (sp > -1) {
int ep = val.indexOf(END, sp + 1);
if (ep > -1) {
return eval(val, sp, ep, map);
}
}
return val;
}
/**
* Convert the expression using JNDI, Environment variables, System Properties
* or existing an property in SystemProperties itself.
*/
private static String evaluateExpression(String exp, PropertyMap map) {
if (isJndiExpression(exp)) {
// JNDI property lookup...
String val = getJndiProperty(exp);
if (val != null) {
return val;
}
}
// check Environment Variables first
String val = System.getenv(exp);
if (val == null) {
// then check system properties
val = System.getProperty(exp);
}
if (val == null && map != null) {
// then check PropertyMap
val = map.get(exp);
}
if (val != null) {
return val;
} else {
// unable to evaluate yet... but maybe later based on the order
// in which properties are being set/loaded. You can use
// GlobalProperties.evaluateExpressions() to get any unresolved
// expressions to be evaluated
logger.debug("Unable to evaluate expression [{}]", exp);
return null;
}
}
private static String eval(String val, int sp, int ep, PropertyMap map) {
StringBuilder sb = new StringBuilder();
sb.append(val.substring(0, sp));
String cal = evalExpression(val, sp, ep, map);
sb.append(cal);
eval(val, ep + 1, sb, map);
return sb.toString();
}
private static void eval(String val, int startPos, StringBuilder sb, PropertyMap map) {
if (startPos < val.length()) {
int sp = val.indexOf(START, startPos);
if (sp > -1) {
// append what is between the last token and the new one (if startPos ==
// sp nothing gets added)
sb.append(val.substring(startPos, sp));
int ep = val.indexOf(END, sp + 1);
if (ep > -1) {
String cal = evalExpression(val, sp, ep, map);
sb.append(cal);
eval(val, ep + 1, sb, map);
return;
}
}
}
// append what is left...
sb.append(val.substring(startPos));
}
private static String evalExpression(String val, int sp, int ep, PropertyMap map) {
// trim off start and end ${ and }
String exp = val.substring(sp + START.length(), ep);
// evaluate the variable
String evaled = evaluateExpression(exp, map);
if (evaled != null) {
return evaled;
} else {
// unable to evaluate at this stage (maybe later)
return START + exp + END;
}
}
private static boolean isJndiExpression(String exp) {
return exp.startsWith("JNDI:") || exp.startsWith("jndi:");
}
/**
* Returns null if JNDI is not setup or if the property is not found.
*
* @param key the key of the JNDI Environment property including a JNDI: prefix.
*/
private static String getJndiProperty(String key) {
try {
// remove the JNDI: prefix
key = key.substring(5);
return (String) getJndiObject(key);
} catch (NamingException ex) {
return null;
}
}
/**
* Similar to getProperty but throws NamingException if JNDI is not setup or
* if the property is not found.
*/
private static Object getJndiObject(String key) throws NamingException {
InitialContext ctx = new InitialContext();
return ctx.lookup(JAVA_COMP_ENV + key);
}
}
@@ -1,168 +0,0 @@
package io.ebean.config;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
/**
* A map like structure of properties.
* <p/>
* Handles evaluation of expressions like ${home} and provides convenience methods for int, long and boolean.
*/
public final class PropertyMap implements Serializable {
private static final long serialVersionUID = 1L;
private final LinkedHashMap<String, String> map = new LinkedHashMap<>();
/**
* Return properties loaded from test-ebean.properties.
*/
public static Properties testProperties() {
PropertyMap propertyMap = PropertyMapLoader.loadTestProperties();
return (propertyMap == null) ? new Properties() : propertyMap.asProperties();
}
public static Properties defaultProperties() {
PropertyMap propertyMap = PropertyMapLoader.loadGlobalProperties();
return (propertyMap == null) ? new Properties() : propertyMap.asProperties();
}
public static boolean loadTestProperties() {
String disable = System.getProperty("disableTestProperties");
return (disable == null || !disable.equalsIgnoreCase("true"));
}
@Override
public String toString() {
return map.toString();
}
/**
* Return as standard Properties.
*/
public Properties asProperties() {
Properties properties = new Properties();
for (Entry<String, String> e : entrySet()) {
properties.put(e.getKey(), e.getValue());
}
return properties;
}
/**
* Go through all the properties and evaluate any expressions that have not
* been resolved.
*/
public void evaluateProperties() {
for (Entry<String, String> e : entrySet()) {
String key = e.getKey();
String val = e.getValue();
String eval = eval(val);
if (eval != null && !eval.equals(val)) {
put(key, eval);
}
}
}
/**
* Returns the value with expressions like ${home} evaluated using system properties and environment variables.
*/
public synchronized String eval(String val) {
return PropertyExpression.eval(val, this);
}
/**
* Return the boolean property value with a given default.
*/
public synchronized boolean getBoolean(String key, boolean defaultValue) {
String value = get(key);
if (value == null) {
return defaultValue;
} else {
return Boolean.parseBoolean(value);
}
}
/**
* Return the int property value with a given default.
*/
public synchronized int getInt(String key, int defaultValue) {
String value = get(key);
if (value == null) {
return defaultValue;
} else {
return Integer.parseInt(value);
}
}
/**
* Return the long property value with a given default.
*/
public synchronized long getLong(String key, long defaultValue) {
String value = get(key);
if (value == null) {
return defaultValue;
} else {
return Long.parseLong(value);
}
}
/**
* Return the string property value with a given default.
*/
public synchronized String get(String key, String defaultValue) {
String value = map.get(key.toLowerCase());
return value == null ? defaultValue : value;
}
/**
* Return the property value returning null if there is no value defined.
*/
public synchronized String get(String key) {
return map.get(key.toLowerCase());
}
/**
* Put all evaluating any expressions in the values.
*/
public synchronized void putEvalAll(Map<String, String> keyValueMap) {
for (Map.Entry<String, String> entry : keyValueMap.entrySet()) {
putEval(entry.getKey(), entry.getValue());
}
}
/**
* Put a single key value evaluating any expressions in the value.
*/
public synchronized String putEval(String key, String value) {
value = PropertyExpression.eval(value, this);
return map.put(key.toLowerCase(), value);
}
/**
* Put a single key value with no expression evaluation.
*/
public synchronized String put(String key, String value) {
return map.put(key.toLowerCase(), value);
}
/**
* Remove an entry.
*/
public synchronized String remove(String key) {
return map.remove(key.toLowerCase());
}
/**
* Return the entries.
*/
public synchronized Set<Entry<String, String>> entrySet() {
return map.entrySet();
}
}
@@ -1,163 +0,0 @@
package io.ebean.config;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Properties;
/**
* Helper used to load the ebean.properties into a PropertyMap.
*/
final class PropertyMapLoader {
private static final Logger logger = LoggerFactory.getLogger(PropertyMapLoader.class);
private static final Pattern OTHER_PROPS_REPLACE = Pattern.compile("\\", Pattern.LITERAL);
/**
* Load the <code>test-ebean.properties</code>.
*/
public static PropertyMap loadTestProperties() {
if (!PropertyMap.loadTestProperties()) {
return new PropertyMap();
}
return load(null, "test-ebean.properties");
}
/**
* Load the ebean.properties (and test-ebean.properties if present).
*/
public static PropertyMap loadGlobalProperties() {
boolean loadTestProperties = false;
String fileName = System.getenv("EBEAN_PROPS_FILE");
if (fileName == null) {
fileName = System.getProperty("ebean.props.file");
if (fileName == null) {
loadTestProperties = PropertyMap.loadTestProperties();
fileName = "ebean.properties";
}
}
PropertyMap map = load(null, fileName);
if (loadTestProperties) {
// load test properties if present in classpath
map = load(map, "test-ebean.properties");
}
return map;
}
/**
* Load the file returning the property map.
*
* @param p an existing property map to load into.
* @param fileName the name of the properties file to load.
*/
public static PropertyMap load(PropertyMap p, String fileName) {
InputStream is = findInputStream(fileName);
if (is == null) {
return p;
} else {
return load(p, is);
}
}
/**
* Load the InputStream returning the property map.
*
* @param p an existing property map to load into.
* @param in the InputStream of the properties file to load.
*/
public static PropertyMap load(PropertyMap p, InputStream in) {
Properties props = new Properties();
try {
props.load(in);
in.close();
return load(p, props);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static PropertyMap load(PropertyMap p, Properties props) {
if (p == null) {
p = new PropertyMap();
}
// put values in initially without any evaluation
for (Map.Entry<Object, Object> entry : props.entrySet()) {
String key = ((String) entry.getKey()).toLowerCase();
String val = ((String) entry.getValue());
if (val != null) {
val = val.trim();
}
p.put(key, val);
}
p.evaluateProperties();
String otherProps = p.remove("load.properties");
if (otherProps == null) {
otherProps = p.remove("load.properties.override");
}
if (otherProps != null) {
otherProps = OTHER_PROPS_REPLACE.matcher(otherProps).replaceAll(Matcher.quoteReplacement("/"));
InputStream is = findInputStream(otherProps);
if (is != null) {
logger.debug("loading properties from {}", otherProps);
load(p, is);
} else {
logger.error("load.properties " + otherProps + " not found.");
}
}
return p;
}
/**
* Find the input stream given the file name.
*/
private static InputStream findInputStream(String fileName) {
if (fileName == null) {
throw new NullPointerException("fileName is null?");
}
try {
File f = new File(fileName);
if (f.exists()) {
logger.debug("{} found in file system", fileName);
return new FileInputStream(f);
} else {
InputStream in = findInClassPath(fileName);
if (in != null) {
logger.debug("{} found in classpath", fileName);
}
return in;
}
} catch (FileNotFoundException ex) {
// already made the check so this
// should never be thrown
throw new RuntimeException(ex);
}
}
private static InputStream findInClassPath(String fileName) {
return Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);
}
}
@@ -12,6 +12,8 @@ import io.ebean.cache.ServerCachePlugin;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbEncrypt;
import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import io.ebean.config.properties.PropertiesLoader;
import io.ebean.event.BeanFindController;
import io.ebean.event.BeanPersistController;
import io.ebean.event.BeanPersistListener;
@@ -181,6 +183,11 @@ public class ServerConfig {
*/
private DatabasePlatform databasePlatform;
/**
* The preferred IdType (to override the default Platform type).
*/
private IdType idType;
/**
* For DB's using sequences this is the number of sequence values prefetched.
*/
@@ -463,6 +470,12 @@ public class ServerConfig {
*/
private boolean disableL2Cache;
/**
* Generally we want to perform L2 cache notification in the background and not impact
* the performance of executing transactions.
*/
private boolean notifyL2CacheInForeground;
/**
* The time in millis used to determine when a query is alerted for being slow.
*/
@@ -473,9 +486,13 @@ public class ServerConfig {
*/
private SlowQueryListener slowQueryListener;
private ProfilingConfig profilingConfig = new ProfilingConfig();
/**
* Controls the default order by id setting of queries. See {@link Query#orderById(boolean)}
*/
private boolean defaultOrderById = false;
/**
* Construct a Server Configuration for programmatically creating an EbeanServer.
*/
@@ -511,6 +528,21 @@ public class ServerConfig {
this.slowQueryListener = slowQueryListener;
}
/**
* Sets the default orderById setting for queries.
*/
public void setDefaultOrderById(boolean defaultOrderById) {
this.defaultOrderById = defaultOrderById;
}
/**
* Returns the default orderById setting for queries.
*/
public boolean isDefaultOrderById() {
return defaultOrderById;
}
/**
* Put a service object into configuration such that it can be passed to a plugin.
* <p>
@@ -1716,6 +1748,20 @@ public class ServerConfig {
this.databasePlatform = databasePlatform;
}
/**
* Return the preferred DB platform IdType.
*/
public IdType getIdType() {
return idType;
}
/**
* Set the preferred DB platform IdType.
*/
public void setIdType(IdType idType) {
this.idType = idType;
}
/**
* Return the EncryptKeyManager.
*/
@@ -2551,7 +2597,7 @@ public class ServerConfig {
* Load settings from ebean.properties.
*/
public void loadFromProperties() {
loadFromProperties(PropertyMap.defaultProperties());
loadFromProperties(PropertiesLoader.load());
}
/**
@@ -2560,23 +2606,33 @@ public class ServerConfig {
public void loadFromProperties(Properties properties) {
// keep the properties used for configuration so that these are available for plugins
this.properties = properties;
autoConfiguration();
PropertiesWrapper p = new PropertiesWrapper("ebean", name, properties);
loadSettings(p);
}
/**
* Use a 'plugin' to provide automatic configuration. Intended for automatic testing
* configuration with Docker containers via ebean-test-config.
*/
private void autoConfiguration() {
for (AutoConfigure autoConfigure : serviceLoad(AutoConfigure.class)) {
autoConfigure.configure(this);
}
}
/**
* 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() {
Properties properties = PropertyMap.testProperties();
if (!properties.isEmpty()) {
PropertiesWrapper p = new PropertiesWrapper("ebean", name, properties);
loadSettings(p);
}
// do nothing now ... as we always try to read test configuration and that should only
}
/**
@@ -2680,6 +2736,7 @@ public class ServerConfig {
slowQueryMillis = p.getLong("slowQueryMillis", slowQueryMillis);
docStoreOnly = p.getBoolean("docStoreOnly", docStoreOnly);
disableL2Cache = p.getBoolean("disableL2Cache", disableL2Cache);
notifyL2CacheInForeground = p.getBoolean("notifyL2CacheInForeground", notifyL2CacheInForeground);
explicitTransactionBeginMode = p.getBoolean("explicitTransactionBeginMode", explicitTransactionBeginMode);
autoCommitMode = p.getBoolean("autoCommitMode", autoCommitMode);
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
@@ -2716,6 +2773,7 @@ public class ServerConfig {
throw new IllegalArgumentException("Property 'batch.mode' or 'persistBatching' is being set but no longer used. Please change to use 'persistBatchMode'");
}
idType = p.getEnum(IdType.class, "idType", idType);
persistBatch = p.getEnum(PersistBatch.class, "persistBatch", persistBatch);
persistBatchOnCascade = p.getEnum(PersistBatch.class, "persistBatchOnCascade", persistBatchOnCascade);
@@ -2740,6 +2798,7 @@ public class ServerConfig {
databaseBooleanTrue = p.get("databaseBooleanTrue", databaseBooleanTrue);
databaseBooleanFalse = p.get("databaseBooleanFalse", databaseBooleanFalse);
databasePlatformName = p.get("databasePlatformName", databasePlatformName);
defaultOrderById = p.getBoolean("defaultOrderById", defaultOrderById);
DbUuid dbUuid = p.getEnum(DbUuid.class, "dbuuid", null);
if (dbUuid != null) {
@@ -2900,6 +2959,25 @@ public class ServerConfig {
this.disableL2Cache = disableL2Cache;
}
/**
* Return true if L2 cache notification should run in the foreground.
*/
public boolean isNotifyL2CacheInForeground() {
return notifyL2CacheInForeground;
}
/**
* Set this to true to run L2 cache notification in the foreground.
* <p>
* In general we don't want to do that as when we use a distributed cache (like Ignite, Hazelcast etc)
* we are making network calls and we prefer to do this in background and not impact the response time
* of the executing transaction.
* </p>
*/
public void setNotifyL2CacheInForeground(boolean notifyL2CacheInForeground) {
this.notifyL2CacheInForeground = notifyL2CacheInForeground;
}
/**
* Return the query plan time to live.
*/
@@ -6,6 +6,8 @@ import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.Platform;
import io.ebean.config.CustomDbTypeMapping;
import io.ebean.config.DbTypeConfig;
import io.ebean.config.ServerConfig;
import io.ebean.util.JdbcClose;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -98,6 +100,10 @@ public class DatabasePlatform {
*/
protected DbIdentity dbIdentity = new DbIdentity();
protected boolean sequenceBatchMode = true;
protected int sequenceBatchSize = 20;
/**
* The history support for this database platform.
*/
@@ -140,9 +146,25 @@ public class DatabasePlatform {
private static final char BACK_TICK = '`';
/**
* The like clause. Can be overridden to disable default escape character.
* The non-escaped like clause (to stop slash being escaped on some platforms).
* Used for the 'raw like' expression but not for startsWith, endsWith and contains expressions.
*/
protected String likeClause = "like ?";
protected String likeClauseRaw = "like ? escape''";
/**
* Escaped like clause for startsWith, endsWith and contains.
*/
protected String likeClauseEscaped = "like ? escape'|'";
/**
* Escape character used for startsWith, endsWith and contains.
*/
protected char likeEscapeChar = '|';
/**
* Characters escaped for startsWith, endsWith and contains.
*/
protected char[] likeSpecialCharacters = { '%', '_', '|' };
protected DbEncrypt dbEncrypt;
@@ -177,8 +199,6 @@ public class DatabasePlatform {
protected SqlExceptionTranslator exceptionTranslator = new SqlCodeTranslator();
protected char[] specialLikeCharacters = { '%', '_', '\\' };
/**
* Instantiates a new database platform.
*/
@@ -192,10 +212,19 @@ public class DatabasePlatform {
return exceptionTranslator.translate(message, e);
}
/**
* Configure the platform given the server configuration.
*/
public void configure(ServerConfig config) {
this.sequenceBatchSize = config.getDatabaseSequenceBatchSize();
configureIdType(config.getIdType());
configure(config.getDbTypeConfig(), config.isAllQuotedIdentifiers());
}
/**
* Configure UUID Storage etc based on ServerConfig settings.
*/
public void configure(DbTypeConfig config, boolean allQuotedIdentifiers) {
protected void configure(DbTypeConfig config, boolean allQuotedIdentifiers) {
this.allQuotedIdentifiers = allQuotedIdentifiers;
addGeoTypes(config.getGeometrySRID());
configureIdType(config.getIdType());
@@ -249,6 +278,20 @@ public class DatabasePlatform {
return platform.name().toLowerCase();
}
/**
* Return true if we are using Sequence batch mode rather than STEP.
*/
public boolean isSequenceBatchMode() {
return sequenceBatchMode;
}
/**
* Set to false to not use sequence batch mode but instead STEP mode.
*/
public void setSequenceBatchMode(boolean sequenceBatchMode) {
this.sequenceBatchMode = sequenceBatchMode;
}
/**
* Return true if this database platform supports native ILIKE expression.
*/
@@ -290,10 +333,10 @@ public class DatabasePlatform {
* @param be the BackgroundExecutor that can be used to load the sequence if
* desired
* @param ds the DataSource
* @param stepSize the sequence allocation size as defined by mapping (defaults to 50)
* @param seqName the name of the sequence
* @param batchSize the number of sequences that should be loaded
*/
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, int stepSize, String seqName) {
return null;
}
@@ -572,6 +615,14 @@ public class DatabasePlatform {
return sql;
}
/**
* For update hint on the FROM clause (SQL server only).
*/
public String fromForUpdate(Query.ForUpdate forUpdateMode) {
// return null except for sql server
return null;
}
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode) {
// silently assume the database does not support the "for update" clause.
logger.info("it seems your database does not support the 'for update' clause");
@@ -583,8 +634,8 @@ public class DatabasePlatform {
* <p>
* This may include an escape clause to disable a default escape character.
*/
public String getLikeClause() {
return likeClause;
public String getLikeClause(boolean rawLikeExpression) {
return rawLikeExpression ? likeClauseRaw : likeClauseEscaped;
}
/**
@@ -604,18 +655,7 @@ public class DatabasePlatform {
try {
return tables.next();
} finally {
close(tables);
}
}
/**
* Close the resultSet.
*/
protected void close(ResultSet resultSet) {
try {
resultSet.close();
} catch (SQLException e) {
logger.error("Error closing resultSet", e);
JdbcClose.close(tables);
}
}
@@ -627,7 +667,7 @@ public class DatabasePlatform {
for (int i = 0; i < value.length(); i++) {
char ch = value.charAt(i);
boolean escaped = false;
for (char escapeChar: specialLikeCharacters) {
for (char escapeChar: likeSpecialCharacters) {
if (ch == escapeChar) {
if (sb == null) {
sb = new StringBuilder(value.substring(0, i));
@@ -649,6 +689,6 @@ public class DatabasePlatform {
}
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
sb.append('\\').append(ch);
sb.append(likeEscapeChar).append(ch);
}
}
@@ -126,27 +126,33 @@ public class DbPlatformType implements ExtraDbTypes {
StringBuilder sb = new StringBuilder();
sb.append(name);
if (canHaveLength || !strict) {
// see if there is a precision/scale to add (or not)
int len = deployLength != 0 ? deployLength : defaultLength;
if (len == Integer.MAX_VALUE) {
sb.append("(max)"); // TODO: this is sqlserver specific
} else if (len > 0) {
sb.append("(");
sb.append(len);
int scale = deployScale != 0 ? deployScale : defaultScale;
if (scale > 0) {
sb.append(",");
sb.append(scale);
}
sb.append(")");
}
renderLengthScale(deployLength, deployScale, sb);
}
return sb.toString();
}
/**
* Render the length and scale part of the column definition.
*/
protected void renderLengthScale(int deployLength, int deployScale, StringBuilder sb) {
// see if there is a precision/scale to add (or not)
int len = deployLength != 0 ? deployLength : defaultLength;
if (len == Integer.MAX_VALUE) {
sb.append("(max)"); // TODO: this is sqlserver specific
} else if (len > 0) {
sb.append("(");
sb.append(len);
int scale = deployScale != 0 ? deployScale : defaultScale;
if (scale > 0) {
sb.append(",");
sb.append(scale);
}
sb.append(")");
}
}
/**
* Create a copy of the type with a new default length.
*/
@@ -10,8 +10,23 @@ import java.util.Map;
*/
public class DbPlatformTypeMapping {
/**
* Boolean type used for logical model.
*/
private static class BooleanLogicalType extends DbPlatformType {
BooleanLogicalType() {
super("boolean", false);
}
@Override
protected void renderLengthScale(int deployLength, int deployScale, StringBuilder sb) {
// do not have length - even if platform boolean type does like integer(1)
}
}
private static DbPlatformTypeLookup lookup = new DbPlatformTypeLookup();
private static final DbPlatformType BOOLEAN_LOGICAL = new BooleanLogicalType();
private static final DbPlatformType UUID_NATIVE = new DbPlatformType("uuid", false);
@SuppressWarnings("unused")
private static final DbPlatformType UUID_PLACEHOLDER = new DbPlatformType("uuidPlaceholder");
@@ -51,7 +66,7 @@ public class DbPlatformTypeMapping {
*/
private void loadDefaults(boolean logicalTypes) {
put(DbType.BOOLEAN);
put(DbType.BOOLEAN, BOOLEAN_LOGICAL);
put(DbType.BIT);
put(DbType.INTEGER);
put(DbType.BIGINT);
@@ -0,0 +1,57 @@
package io.ebean.config.dbplatform;
import io.ebean.BackgroundExecutor;
import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/**
* Database sequence based IdGenerator using Sequence Step 1 but batch fetch many sequence values.
*/
public abstract class SequenceBatchIdGenerator extends SequenceIdGenerator {
/**
* Construct where batchSize is the sequence step size.
*
*/
public SequenceBatchIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
super(be, ds, seqName, batchSize);
}
/**
* If allocateSize is large load some sequences in a background thread.
* <p>
* For example, when inserting a bean with a cascade on a OneToMany with many
* beans Ebean can call this to ensure .
* </p>
*/
@Override
public void preAllocateIds(int requestSize) {
if (allocationSize > 1 && requestSize > allocationSize) {
// only bother if allocateSize is bigger than
// the normal loading batchSize
if (requestSize > 100) {
// max out at 100 for now
requestSize = 100;
}
loadInBackground(requestSize);
}
}
/**
* Add the next set of Ids as the next value plus all the following numbers up to the step size.
*/
@Override
protected List<Long> readIds(ResultSet resultSet, int loadSize) throws SQLException {
List<Long> newIds = new ArrayList<>(loadSize);
while (resultSet.next()) {
newIds.add(resultSet.getLong(1));
}
return newIds;
}
}
@@ -2,6 +2,7 @@ package io.ebean.config.dbplatform;
import io.ebean.BackgroundExecutor;
import io.ebean.Transaction;
import io.ebean.util.JdbcClose;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -12,13 +13,15 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Database sequence based IdGenerator.
*/
public abstract class SequenceIdGenerator implements PlatformIdGenerator {
private static final Logger logger = LoggerFactory.getLogger(SequenceIdGenerator.class);
protected static final Logger logger = LoggerFactory.getLogger("io.ebean.SEQ");
/**
* Used to synchronise the idList access.
@@ -41,18 +44,18 @@ public abstract class SequenceIdGenerator implements PlatformIdGenerator {
protected final ArrayList<Long> idList = new ArrayList<>(50);
protected final int batchSize;
protected final int allocationSize;
protected int currentlyBackgroundLoading;
protected boolean currentlyBackgroundLoading;
/**
* Construct given a dataSource and sql to return the next sequence value.
*/
public SequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
protected SequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int allocationSize) {
this.backgroundExecutor = be;
this.dataSource = ds;
this.seqName = seqName;
this.batchSize = batchSize;
this.allocationSize = allocationSize;
}
public abstract String getSql(int batchSize);
@@ -81,26 +84,8 @@ public abstract class SequenceIdGenerator implements PlatformIdGenerator {
* </p>
*/
@Override
public void preAllocateIds(int allocateSize) {
if (batchSize > 1 && allocateSize > batchSize) {
// only bother if allocateSize is bigger than
// the normal loading batchSize
if (allocateSize > 100) {
// max out at 100 for now
allocateSize = 100;
}
loadLargeAllocation(allocateSize);
}
}
/**
* Called by preAllocateIds when we know that a large number of Id's is going
* to be needed shortly.
*/
protected void loadLargeAllocation(final int allocateSize) {
// preAllocateIds was called with a relatively large batchSize
// so we will just go ahead and load those anyway in background
backgroundExecutor.execute(() -> loadMoreIds(allocateSize, null));
public void preAllocateIds(int requestSize) {
// do nothing by default
}
/**
@@ -113,80 +98,80 @@ public abstract class SequenceIdGenerator implements PlatformIdGenerator {
public Object nextId(Transaction t) {
synchronized (monitor) {
if (idList.isEmpty()) {
loadMoreIds(batchSize, t);
}
Long nextId = idList.remove(0);
if (batchSize > 1) {
if (idList.size() <= batchSize / 2) {
loadBatchInBackground();
}
int size = idList.size();
if (size > 0) {
maybeLoadMoreInBackground(size);
} else {
loadMore(allocationSize);
}
return nextId;
return idList.remove(0);
}
}
/**
* Load another batch of Id's using a background thread.
*/
protected void loadBatchInBackground() {
// single threaded processing...
synchronized (backgroundLoadMonitor) {
if (currentlyBackgroundLoading > 0) {
// skip as already background loading
logger.debug("... skip background sequence load (another load in progress)");
return;
private void maybeLoadMoreInBackground(int currentSize) {
if (allocationSize > 1) {
if (currentSize <= allocationSize / 2) {
loadInBackground(allocationSize);
}
currentlyBackgroundLoading = batchSize;
backgroundExecutor.execute(() -> {
loadMoreIds(batchSize, null);
synchronized (backgroundLoadMonitor) {
currentlyBackgroundLoading = 0;
}
});
}
}
protected void loadMoreIds(final int numberToLoad, Transaction t) {
ArrayList<Long> newIds = getMoreIds(numberToLoad, t);
if (logger.isDebugEnabled()) {
logger.debug("... seq:" + seqName + " loaded:" + numberToLoad + " ids:" + newIds);
}
private void loadMore(int requestSize) {
List<Long> newIds = getMoreIds(requestSize);
synchronized (monitor) {
idList.addAll(newIds);
}
}
/**
* Load another batch of Id's using a background thread.
*/
protected void loadInBackground(final int requestSize) {
// single threaded processing...
synchronized (backgroundLoadMonitor) {
if (currentlyBackgroundLoading) {
// skip as already background loading
logger.debug("... skip background sequence load (another load in progress)");
return;
}
currentlyBackgroundLoading = true;
backgroundExecutor.execute(() -> {
loadMore(requestSize);
synchronized (backgroundLoadMonitor) {
currentlyBackgroundLoading = false;
}
});
}
}
/**
* Read the resultSet returning the list of Id values.
*/
protected abstract List<Long> readIds(ResultSet resultSet, int loadSize) throws SQLException;
/**
* Get more Id's by executing a query and reading the Id's returned.
*/
protected ArrayList<Long> getMoreIds(int loadSize, Transaction t) {
protected List<Long> getMoreIds(int requestSize) {
String sql = getSql(loadSize);
String sql = getSql(requestSize);
ArrayList<Long> newIds = new ArrayList<>(loadSize);
boolean useTxnConnection = t != null;
Connection c = null;
PreparedStatement pstmt = null;
ResultSet rset = null;
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
c = useTxnConnection ? t.getConnection() : dataSource.getConnection();
connection = dataSource.getConnection();
pstmt = c.prepareStatement(sql);
rset = pstmt.executeQuery();
while (rset.next()) {
newIds.add(rset.getLong(1));
statement = connection.prepareStatement(sql);
resultSet = statement.executeQuery();
List<Long> newIds = readIds(resultSet, requestSize);
if (logger.isTraceEnabled()) {
logger.trace("seq:{} loaded:{} sql:{}", seqName, newIds.size(), sql);
}
if (newIds.isEmpty()) {
throw new PersistenceException("Always expecting more than 1 row from " + sql);
@@ -197,46 +182,24 @@ public abstract class SequenceIdGenerator implements PlatformIdGenerator {
} catch (SQLException e) {
if (e.getMessage().contains("Database is already closed")) {
String msg = "Error getting SEQ when DB shutting down " + e.getMessage();
logger.info(msg);
logger.error(msg);
System.out.println(msg);
return newIds;
return Collections.emptyList();
} else {
throw new PersistenceException("Error getting sequence nextval", e);
}
} finally {
if (useTxnConnection) {
closeResources(null, pstmt, rset);
} else {
closeResources(c, pstmt, rset);
}
closeResources(connection, statement, resultSet);
}
}
/**
* Close the JDBC resources.
*/
protected void closeResources(Connection c, PreparedStatement pstmt, ResultSet rset) {
try {
if (rset != null) {
rset.close();
}
} catch (SQLException e) {
logger.error("Error closing ResultSet", e);
}
try {
if (pstmt != null) {
pstmt.close();
}
} catch (SQLException e) {
logger.error("Error closing PreparedStatement", e);
}
try {
if (c != null) {
c.close();
}
} catch (SQLException e) {
logger.error("Error closing Connection", e);
}
private void closeResources(Connection connection, PreparedStatement statement, ResultSet resultSet) {
JdbcClose.close(resultSet);
JdbcClose.close(statement);
JdbcClose.close(connection);
}
}
@@ -0,0 +1,39 @@
package io.ebean.config.dbplatform;
import io.ebean.BackgroundExecutor;
import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/**
* Database sequence based IdGenerator using Sequence Step (e.g. step 50).
*/
public abstract class SequenceStepIdGenerator extends SequenceIdGenerator {
/**
* Construct with stepSize (typically 50).
*/
protected SequenceStepIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int stepSize) {
super(be, ds, seqName, stepSize);
}
/**
* Add the next set of Ids as the next value plus all the following numbers up to the step size.
*/
@Override
protected List<Long> readIds(ResultSet resultSet, int ignoreRequestSize) throws SQLException {
List<Long> newIds = new ArrayList<>(allocationSize);
if (resultSet.next()) {
long start = resultSet.getLong(1);
for (int i = 0; i < allocationSize; i++) {
newIds.add(start + i);
}
}
return newIds;
}
}
@@ -1,6 +1,7 @@
package io.ebean.config.dbplatform;
import io.ebean.Transaction;
import io.ebean.util.JdbcClose;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -82,27 +83,9 @@ public class SimpleSequenceIdGenerator implements PlatformIdGenerator {
}
private void closeResources(ResultSet rset, PreparedStatement pstmt, Connection c) {
try {
if (rset != null) {
rset.close();
}
} catch (SQLException e) {
logger.error("Error closing ResultSet", e);
}
try {
if (pstmt != null) {
pstmt.close();
}
} catch (SQLException e) {
logger.error("Error closing PreparedStatement", e);
}
try {
if (c != null) {
c.close();
}
} catch (SQLException e) {
logger.error("Error closing Connection", e);
}
JdbcClose.close(rset);
JdbcClose.close(pstmt);
JdbcClose.close(c);
}
}
@@ -26,10 +26,7 @@ public class DB2Platform extends DatabasePlatform {
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsSequence(true);
this.likeClause = "like ? escape '|'";
this.specialLikeCharacters = new char[] { '%', '_', '|' };
this.exceptionTranslator =
new SqlErrorCodes()
.addAcquireLock("40001","57033") // key -911/-913
@@ -49,19 +46,14 @@ public class DB2Platform extends DatabasePlatform {
persistBatchOnCascade = PersistBatch.NONE;
}
@Override
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
sb.append('|').append(ch);
}
/**
* Return a DB2 specific sequence IdGenerator that supports batch fetching
* sequence values.
*/
@Override
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be,
DataSource ds, String seqName, int batchSize) {
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, int stepSize, String seqName) {
return new DB2SequenceIdGenerator(be, ds, seqName, batchSize);
return new DB2SequenceIdGenerator(be, ds, seqName, sequenceBatchSize);
}
}
@@ -1,14 +1,14 @@
package io.ebean.config.dbplatform.db2;
import io.ebean.BackgroundExecutor;
import io.ebean.config.dbplatform.SequenceIdGenerator;
import io.ebean.config.dbplatform.SequenceBatchIdGenerator;
import javax.sql.DataSource;
/**
* DB2 specific sequence Id Generator.
*/
public class DB2SequenceIdGenerator extends SequenceIdGenerator {
public class DB2SequenceIdGenerator extends SequenceBatchIdGenerator {
private final String baseSql;
private final String unionBaseSql;
@@ -46,10 +46,8 @@ public class H2Platform extends DatabasePlatform {
* sequence values.
*/
@Override
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds,
String seqName, int batchSize) {
return new H2SequenceIdGenerator(be, ds, seqName, batchSize);
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, int stepSize, String seqName) {
return new H2SequenceIdGenerator(be, ds, seqName, sequenceBatchSize);
}
@Override
@@ -1,14 +1,14 @@
package io.ebean.config.dbplatform.h2;
import io.ebean.BackgroundExecutor;
import io.ebean.config.dbplatform.SequenceIdGenerator;
import io.ebean.config.dbplatform.SequenceBatchIdGenerator;
import javax.sql.DataSource;
/**
* H2 specific sequence Id Generator.
*/
public class H2SequenceIdGenerator extends SequenceIdGenerator {
public class H2SequenceIdGenerator extends SequenceBatchIdGenerator {
private final String baseSql;
private final String unionBaseSql;
@@ -31,9 +31,8 @@ public class HsqldbPlatform extends DatabasePlatform {
}
@Override
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
return new H2SequenceIdGenerator(be, ds, seqName, batchSize);
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, int stepSize, String seqName) {
return new H2SequenceIdGenerator(be, ds, seqName, sequenceBatchSize);
}
}
@@ -1,16 +1,13 @@
package io.ebean.config.dbplatform.mysql;
import io.ebean.BackgroundExecutor;
import io.ebean.Query;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import io.ebean.config.dbplatform.PlatformIdGenerator;
import io.ebean.config.dbplatform.SqlErrorCodes;
import javax.sql.DataSource;
import java.sql.Types;
/**
@@ -38,6 +35,11 @@ public class MySqlPlatform extends DatabasePlatform {
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsIdentity(true);
this.dbIdentity.setSupportsSequence(false);
this.dbDefaultValue.setNow("now(6)"); // must have same precision as TIMESTAMP
this.dbDefaultValue.setFalse("0");
this.dbDefaultValue.setTrue("1");
this.exceptionTranslator =
new SqlErrorCodes()
@@ -49,14 +51,14 @@ public class MySqlPlatform extends DatabasePlatform {
this.openQuote = "`";
this.closeQuote = "`";
// use pipe for escaping as it depends if mysql runs in no_backslash_escapes or not.
this.likeClause = "like binary ? escape '|'";
this.specialLikeCharacters = new char[] { '%', '_', '|' };
this.likeClauseRaw = "like binary ? escape ''";
this.likeClauseEscaped = "like binary ? escape '|'";
this.forwardOnlyHintOnFindIterate = true;
this.booleanDbType = Types.BIT;
dbTypeMap.put(DbType.BIT, new DbPlatformType("tinyint(1) default 0"));
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("tinyint(1) default 0"));
dbTypeMap.put(DbType.BIT, new DbPlatformType("tinyint(1)"));
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("tinyint(1)"));
dbTypeMap.put(DbType.TIMESTAMP, new DbPlatformType("datetime(6)"));
dbTypeMap.put(DbType.CLOB, new MySqlClob());
dbTypeMap.put(DbType.BLOB, new MySqlBlob());
@@ -64,24 +66,10 @@ public class MySqlPlatform extends DatabasePlatform {
dbTypeMap.put(DbType.VARBINARY, new DbPlatformType("varbinary", 255));
}
/**
* Return null in case there is a sequence annotation.
*/
@Override
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be,
DataSource ds, String seqName, int batchSize) {
return null;
}
@Override
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode) {
// NOWAIT and SKIP LOCKED currently not supported with MySQL
return sql + " for update";
}
@Override
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
sb.append('|').append(ch);
}
}
@@ -10,12 +10,13 @@ import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import io.ebean.config.dbplatform.PlatformIdGenerator;
import io.ebean.config.dbplatform.RownumSqlLimiter;
import io.ebean.config.dbplatform.SqlErrorCodes;
import javax.sql.DataSource;
import java.sql.Types;
/**
* Oracle10 and greater specific platform.
* Oracle specific platform.
*/
public class OraclePlatform extends DatabasePlatform {
@@ -29,22 +30,30 @@ public class OraclePlatform extends DatabasePlatform {
this.basicSqlLimiter = new BasicSqlAnsiLimiter();
this.historySupport = new OracleDbHistorySupport();
// Not using getGeneratedKeys as instead we will
// batch load sequences which enables JDBC batch execution
dbIdentity.setSupportsGetGeneratedKeys(false);
dbIdentity.setIdType(IdType.SEQUENCE);
dbIdentity.setSupportsSequence(true);
dbIdentity.setSupportsIdentity(true);
dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbDefaultValue.setFalse("0");
this.dbDefaultValue.setTrue("1");
this.dbDefaultValue.setNow("current_timestamp");
this.treatEmptyStringsAsNull = true;
this.likeClauseRaw = "like ?";
this.exceptionTranslator =
new SqlErrorCodes()
//.addAcquireLock("")
.addDuplicateKey("1")
.addDataIntegrity("2291")
.build();
this.likeClause = "like ? escape '|'";
this.specialLikeCharacters = new char[] { '%', '_', '|' };
this.openQuote = "\"";
this.closeQuote = "\"";
booleanDbType = Types.INTEGER;
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("number(1) default 0"));
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("number(1)"));
dbTypeMap.put(DbType.INTEGER, new DbPlatformType("number", 10));
dbTypeMap.put(DbType.BIGINT, new DbPlatformType("number", 19));
@@ -65,9 +74,8 @@ public class OraclePlatform extends DatabasePlatform {
}
@Override
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
return new OracleSequenceIdGenerator(be, ds, seqName, batchSize);
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, int stepSize, String seqName) {
return new OracleSequenceIdGenerator(be, ds, seqName, sequenceBatchSize);
}
@Override
@@ -81,9 +89,5 @@ public class OraclePlatform extends DatabasePlatform {
return sql + " for update";
}
}
@Override
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
sb.append('|').append(ch);
}
}
@@ -1,14 +1,14 @@
package io.ebean.config.dbplatform.oracle;
import io.ebean.BackgroundExecutor;
import io.ebean.config.dbplatform.SequenceIdGenerator;
import io.ebean.config.dbplatform.SequenceBatchIdGenerator;
import javax.sql.DataSource;
/**
* Oracle specific sequence Id Generator.
*/
public class OracleSequenceIdGenerator extends SequenceIdGenerator {
public class OracleSequenceIdGenerator extends SequenceBatchIdGenerator {
private final String baseSql;
@@ -39,6 +39,8 @@ public class PostgresPlatform extends DatabasePlatform {
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsSequence(true);
this.dbDefaultValue.setNow("current_timestamp");
this.exceptionTranslator =
new SqlErrorCodes()
.addAcquireLock("55P03")
@@ -90,9 +92,9 @@ public class PostgresPlatform extends DatabasePlatform {
* Create a Postgres specific sequence IdGenerator.
*/
@Override
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, int stepSize, String seqName) {
return new PostgresSequenceIdGenerator(be, ds, seqName, batchSize);
return new PostgresSequenceIdGenerator(be, ds, seqName, sequenceBatchSize);
}
@Override
@@ -1,14 +1,14 @@
package io.ebean.config.dbplatform.postgres;
import io.ebean.BackgroundExecutor;
import io.ebean.config.dbplatform.SequenceIdGenerator;
import io.ebean.config.dbplatform.SequenceBatchIdGenerator;
import javax.sql.DataSource;
/**
* Postgres specific sequence Id Generator.
*/
public class PostgresSequenceIdGenerator extends SequenceIdGenerator {
public class PostgresSequenceIdGenerator extends SequenceBatchIdGenerator {
private final String baseSql;
@@ -27,7 +27,7 @@ public class SqlAnywherePlatform extends DatabasePlatform {
this.dbIdentity.setSelectLastInsertedIdTemplate("select @@IDENTITY as X");
this.dbIdentity.setSupportsIdentity(true);
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("bit default 0"));
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("bit"));
dbTypeMap.put(DbType.BIGINT, new DbPlatformType("numeric", 19));
dbTypeMap.put(DbType.REAL, new DbPlatformType("float(16)"));
dbTypeMap.put(DbType.DOUBLE, new DbPlatformType("float(32)"));
@@ -19,12 +19,21 @@ public class SQLitePlatform extends DatabasePlatform {
this.dbIdentity.setSelectLastInsertedIdTemplate("select last_insert_rowid()");
this.booleanDbType = Types.INTEGER;
this.likeClauseRaw = "like ?";
this.likeClauseEscaped = "like ?";
this.dbDefaultValue.setFalse("0");
this.dbDefaultValue.setTrue("1");
this.dbDefaultValue.setNow("CURRENT_TIMESTAMP");
dbTypeMap.put(DbType.BIT, new DbPlatformType("int default 0"));
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("int default 0"));
dbTypeMap.put(DbType.BIT, new DbPlatformType("int"));
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("int"));
dbTypeMap.put(DbType.BIGINT, new DbPlatformType("integer"));
dbTypeMap.put(DbType.SMALLINT, new DbPlatformType("integer"));
}
@Override
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
sb.append(ch);
}
}
@@ -13,7 +13,7 @@ public class SqlServerBasicSqlLimiter implements BasicSqlLimiter {
StringBuilder sb = new StringBuilder(50 + dbSql.length());
sb.append(dbSql);
if (!dbSql.toLowerCase().contains("order by")) {
sb.append(" order by 1 ");
sb.append(" order by 1");
}
sb.append(" ").append("offset");
sb.append(" ").append(firstRow).append(" rows");
@@ -1,13 +1,18 @@
package io.ebean.config.dbplatform.sqlserver;
import io.ebean.BackgroundExecutor;
import io.ebean.Query;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.Platform;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import io.ebean.config.dbplatform.PlatformIdGenerator;
import io.ebean.config.dbplatform.SqlErrorCodes;
import javax.sql.DataSource;
import java.sql.Types;
/**
@@ -18,8 +23,8 @@ public class SqlServerPlatform extends DatabasePlatform {
public SqlServerPlatform() {
super();
this.platform = Platform.SQLSERVER;
// effectively disable persistBatchOnCascade mode for SQL Server
// due to lack of support for getGeneratedKeys in batch mode
// disable persistBatchOnCascade mode for
// SQL Server unless we are using sequences
this.persistBatchOnCascade = PersistBatch.NONE;
this.idInExpandedForm = true;
this.selectCountWithAlias = true;
@@ -29,21 +34,27 @@ public class SqlServerPlatform extends DatabasePlatform {
this.dbIdentity.setIdType(IdType.IDENTITY);
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsIdentity(true);
this.dbIdentity.setSupportsSequence(true);
this.sequenceBatchMode = false;
this.exceptionTranslator =
new SqlErrorCodes()
.addAcquireLock("1222")
.addDuplicateKey("2601", "2627")
.addDataIntegrity("544", "8114", "8115")
.addDataIntegrity("544", "547", "8114", "8115")
.build();
this.openQuote = "[";
this.closeQuote = "]";
this.specialLikeCharacters = new char[]{'%', '_', '['};
this.likeClause = "like ? COLLATE Latin1_General_BIN";
this.likeSpecialCharacters = new char[]{'%', '_', '['};
this.likeClauseRaw = "like ? collate Latin1_General_BIN";
this.likeClauseEscaped = "like ? collate Latin1_General_BIN";
booleanDbType = Types.INTEGER;
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("bit default 0"));
this.dbDefaultValue.setFalse("0");
this.dbDefaultValue.setTrue("1");
this.dbDefaultValue.setNow("SYSUTCDATETIME()");
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("bit"));
dbTypeMap.put(DbType.INTEGER, new DbPlatformType("integer", false));
dbTypeMap.put(DbType.BIGINT, new DbPlatformType("numeric", 19));
@@ -66,8 +77,45 @@ public class SqlServerPlatform extends DatabasePlatform {
}
@Override
public void configure(ServerConfig config) {
super.configure(config);
if (dbIdentity.getIdType() == IdType.SEQUENCE) {
this.persistBatchOnCascade = PersistBatch.ALL;
}
}
@Override
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
sb.append('[').append(ch).append(']');
}
/**
* Create a Postgres specific sequence IdGenerator.
*/
@Override
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, int stepSize, String seqName) {
return new SqlServerStepSequence(be, ds, seqName, stepSize);
}
/**
* For update is part of the FROM clause on the base table for sql server.
*/
@Override
public String fromForUpdate(Query.ForUpdate forUpdateMode) {
switch (forUpdateMode) {
case SKIPLOCKED:
return "with (updlock,readpast)";
case NOWAIT:
return "with (updlock,nowait)";
default:
return "with (updlock)";
}
}
@Override
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode) {
// for update are hints on from clause of base table
return sql;
}
}
@@ -0,0 +1,24 @@
package io.ebean.config.dbplatform.sqlserver;
import io.ebean.BackgroundExecutor;
import io.ebean.config.dbplatform.SequenceStepIdGenerator;
import javax.sql.DataSource;
public class SqlServerStepSequence extends SequenceStepIdGenerator {
private final String nextSql;
/**
* Construct where batchSize is the sequence step size.
*/
public SqlServerStepSequence(BackgroundExecutor be, DataSource ds, String seqName, int stepSize) {
super(be, ds, seqName, stepSize);
this.nextSql = "select next value for "+seqName;
}
@Override
public String getSql(int batchSize) {
return nextSql;
}
}
@@ -0,0 +1,97 @@
package io.ebean.config.properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* Manages the underlying map of properties we are gathering.
*/
class LoadContext {
private static final Logger log = LoggerFactory.getLogger(LoadContext.class);
/**
* Map we are loading the properties into.
*/
private final Map<String, String> map = new LinkedHashMap<>();
/**
* Names of resources/files that were loaded.
*/
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 is = null;
if (source == Loader.Source.RESOURCE) {
is = getClass().getResourceAsStream("/" + resourcePath);
if (is != null) {
loadedResources.add(resourcePath);
}
} else {
File file = new File(resourcePath);
if (file.exists()) {
try {
is = new FileInputStream(file);
loadedResources.add("file:"+resourcePath);
} catch (FileNotFoundException e) {
throw new IllegalStateException(e);
}
}
}
return is;
}
/**
* Add a property entry.
*/
void put(String key, String val) {
if (val != null) {
val = val.trim();
}
map.put(key, val);
}
/**
* Evaluate all the expressions and return as a Properties object.
*/
Properties eval() {
log.info("loaded properties from {}", loadedResources);
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);
}
return properties;
}
/**
* Read the special properties that can point to an external properties source.
*/
String indirectLocation() {
String indirectLocation = map.get("load.properties");
if (indirectLocation == null) {
indirectLocation = map.get("load.properties.override");
}
return indirectLocation;
}
}
@@ -0,0 +1,164 @@
package io.ebean.config.properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Properties;
/**
* Loads the configuration from known/expected locations.
* <p>
* Defines the loading order of resources and files.
* </p>
*/
class Loader {
private static final Logger log = LoggerFactory.getLogger(Loader.class);
enum Source {
RESOURCE,
FILE
}
private final LoadContext loadContext = new LoadContext();
private YamlLoader yamlLoader;
Loader() {
try {
Class<?> exists = Class.forName("org.yaml.snakeyaml.Yaml");
if (exists != null) {
yamlLoader = new YamlLoader(loadContext);
}
} catch (Exception e) {
// ignored
}
}
/**
* Load the configuration with the expected ordering.
*/
void load() {
loadMain(Source.RESOURCE);
// external file configuration overrides the resources configuration
loadMain(Source.FILE);
loadViaSystemProperty();
loadViaIndirection();
// test configuration (if found) overrides main configuration
// we should only find these resources when running tests
loadTest();
}
/**
* Load test configuration.
*/
private void loadTest() {
loadProperties("application-test.properties", Source.RESOURCE);
loadYaml("application-test.yml", Source.RESOURCE);
loadProperties("test-ebean.properties", Source.RESOURCE);
}
/**
* Load configuration defined by a <em>load.properties</em> entry in properties file.
*/
private void loadViaIndirection() {
String location = loadContext.indirectLocation();
if (location != null) {
location = PropertyEval.eval(location);
loadWithExtensionCheck(location);
}
}
/**
* Load the main configuration for the given source.
*/
private void loadMain(Source source) {
loadYaml("application.yml", source);
loadProperties("application.properties", source);
loadProperties("ebean.properties", source);
}
private void loadViaSystemProperty() {
String fileName = System.getenv("EBEAN_PROPS_FILE");
if (fileName == null) {
fileName = System.getProperty("ebean.props.file");
if (fileName != null) {
loadWithExtensionCheck(fileName);
}
}
}
void loadWithExtensionCheck(String fileName) {
if (fileName.endsWith("yml")) {
loadYaml(fileName, Source.FILE);
} else if (fileName.endsWith("properties")) {
loadProperties(fileName, Source.FILE);
} else {
throw new IllegalArgumentException("Expecting only yml or properties file but got [" + fileName + "]");
}
}
/**
* Evaluate all the configuration entries and return as properties.
*/
Properties eval() {
return loadContext.eval();
}
void loadYaml(String resourcePath, Source source) {
if (yamlLoader != null) {
yamlLoader.load(resource(resourcePath, source));
}
}
void loadProperties(String resourcePath, Source source) {
InputStream is = resource(resourcePath, source);
if (is != null) {
loadProperties(is);
}
}
private InputStream resource(String resourcePath, Source source) {
return loadContext.resource(resourcePath, source);
}
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);
}
}
}
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);
}
}
}
@@ -0,0 +1,69 @@
package io.ebean.config.properties;
import java.util.Properties;
/**
* Loads and evaluates properties and yml configuration.
*/
public class PropertiesLoader {
private static Properties properties;
/**
* Provides properties by reading known locations.
* <p>
* <h3>Main configuration</h3>
* <p>
* <p>Firstly loads from main resources</p>
* <pre>
* - application.properties
* - application.yml
* - ebean.properties
* </pre>
* <p>
* <p>Then loads from local files</p>
* <pre>
* - application.properties
* - application.yml
* - ebean.properties
* </pre>
* <p>
* <p>Then loads from environment variable <em>EBEAN_PROPS_FILE</em></p>
* <p>Then loads from system property <em>ebean.props.file</em></p>
* <p>Then loads from <em>load.properties</em></p>
* <p>
* <h3>Test configuration</h3>
* <p>
* Once the main configuration is read it will try to read common test configuration.
* This will only be successful if the test resources are available (i.e. running tests).
* </p>
* <p>Loads from test resources</p>
* <pre>
* - application-test.properties
* - application-test.yml
* - test-ebean.properties
* </pre>
*/
public static synchronized Properties load() {
if (properties == null) {
Loader loader = new Loader();
loader.load();
properties = loader.eval();
}
return properties;
}
/**
* Set (override) a configuration property.
*
* This is expected to be only called by test configuration logic
* (ala automatic test configuration via ebean-test-config).
*/
public static synchronized void setProperty(String key, String value) {
load();
properties.setProperty(key, value);
}
}
@@ -0,0 +1,202 @@
package io.ebean.config.properties;
import javax.naming.InitialContext;
import javax.naming.NamingException;
/**
* Helper used to evaluate expressions such as ${CATALINA_HOME}.
* <p>
* The expressions can contain environment variables, system properties or JNDI
* properties. JNDI expressions take the form ${jndi:propertyName} where you
* substitute propertyName with the name of the jndi property you wish to
* evaluate.
* </p>
*/
final class PropertyEval {
/**
* Prefix for looking up JNDI Environment variable.
*/
private static final String JAVA_COMP_ENV = "java:comp/env/";
/**
* Used to detect the start of an expression.
*/
private static final String START = "${";
/**
* Used to detect the end of an expression.
*/
private static final String END = "}";
/**
* Specify the PropertyHolder.
*/
private PropertyEval() {
}
/**
* Return the property value evaluating and replacing any expressions such as
* ${CATALINA_HOME}.
*/
static String eval(String val) {
if (val == null) {
return null;
}
int sp = val.indexOf(START);
if (sp > -1) {
int ep = val.indexOf(END, sp + 1);
if (ep > -1) {
return eval(val, sp, ep);
}
}
return val;
}
/**
* Convert the expression using JNDI, Environment variables, System Properties
* or existing an property in SystemProperties itself.
*/
private static String evaluateExpression(String exp) {
if (isJndiExpression(exp)) {
// JNDI property lookup...
String val = getJndiProperty(exp);
if (val != null) {
return val;
}
}
String val = System.getProperty(exp);
if (val == null) {
val = System.getenv(exp);
}
return val;
}
private static String eval(String val, int sp, int ep) {
return new EvalBuffer(val, sp, ep).process();
}
private static boolean isJndiExpression(String exp) {
return exp.startsWith("JNDI:") || exp.startsWith("jndi:");
}
/**
* Returns null if JNDI is not setup or if the property is not found.
*
* @param key the key of the JNDI Environment property including a JNDI: prefix.
*/
private static String getJndiProperty(String key) {
try {
// remove the JNDI: prefix
key = key.substring(5);
return (String) getJndiObject(key);
} catch (NamingException ex) {
return null;
}
}
/**
* Similar to getProperty but throws NamingException if JNDI is not setup or
* if the property is not found.
*/
private static Object getJndiObject(String key) throws NamingException {
InitialContext ctx = new InitialContext();
return ctx.lookup(JAVA_COMP_ENV + key);
}
private static class EvalBuffer {
private final StringBuilder buf = new StringBuilder();
private final String original;
private int position;
private int start;
private int end;
private String expression;
private String defaultValue;
EvalBuffer(String val, int start, int ep) {
this.original = val;
this.start = start;
this.end = ep;
this.position = 0;
moveToStart();
}
void moveToStart() {
if (start > position) {
buf.append(original.substring(position, start));
position = start;
}
}
void parseForDefault() {
int colonPos = original.lastIndexOf(':', end);
if (colonPos > start) {
expression = original.substring(start + START.length(), colonPos);
defaultValue = original.substring(colonPos + 1, end);
} else {
expression = original.substring(start + START.length(), end);
}
}
void evaluate() {
String eval = evaluateExpression(expression);
if (eval != null) {
buf.append(eval);
} else {
if (defaultValue != null) {
buf.append(defaultValue);
} else {
buf.append(START).append(expression).append(END);
}
}
}
String end() {
if (end < original.length() - 1) {
buf.append(original.substring(end + 1));
}
return buf.toString();
}
boolean next() {
if (end < original.length()) {
int startPos = original.indexOf(START, end + 1);
if (startPos > -1) {
int endPos = original.indexOf(END, startPos + 1);
if (endPos > -1) {
if (startPos > end + 1) {
buf.append(original.substring(end + 1, startPos));
}
this.start = startPos;
this.end = endPos;
return true;
}
}
}
return false;
}
private void evalNext() {
parseForDefault();
evaluate();
}
String process() {
evalNext();
while (next()) {
evalNext();
}
return end();
}
}
}
@@ -0,0 +1,55 @@
package io.ebean.config.properties;
import org.yaml.snakeyaml.Yaml;
import java.io.InputStream;
import java.util.Map;
/**
* Loads configuration from Yml into the load context.
* <p>
* Note that this ignores 'lists' so just reads 'maps' and scalar values.
* </p>
*/
class YamlLoader {
private final Yaml yaml = new Yaml();
private final LoadContext loadContext;
YamlLoader(LoadContext loadContext) {
this.loadContext = loadContext;
}
void load(InputStream is) {
if (is != null) {
loadMap(yaml.load(is), null);
}
}
@SuppressWarnings("unchecked")
void loadMap(Map<String, Object> map, String path) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
if (path != null) {
key = path + "." + key;
}
Object val = entry.getValue();
if (val instanceof Map) {
loadMap((Map<String, Object>) val, key);
} else {
addScalar(key, val);
}
}
}
private void addScalar(String key, Object val) {
if (val instanceof String) {
loadContext.put(key, (String) val);
} else if (val instanceof Number || val instanceof Boolean) {
loadContext.put(key, val.toString());
}
}
}
@@ -102,6 +102,14 @@ public interface DbMigration {
*/
void addPlatform(Platform platform, String prefix);
/**
* Add an additional databasePlatform to write the migration DDL.
* <p>
* Use this when you want to add preconfigured database platforms.
* </p>
*/
void addDatabasePlatform(DatabasePlatform databasePlatform, String prefix);
/**
* Generate the next migration xml file and associated apply and rollback sql scripts.
* <p>
@@ -0,0 +1,26 @@
package io.ebean.event;
import io.ebean.EbeanServer;
import io.ebean.Transaction;
/**
* A request to delete a bean by Id value.
*/
public interface BeanDeleteIdRequest {
/**
* Return the server processing the request.
*/
EbeanServer getEbeanServer();
/**
* Return the Transaction associated with this request.
*/
Transaction getTransaction();
/**
* Returns the Id value of the bean being deleted.
*/
Object getId();
}
@@ -87,4 +87,11 @@ public abstract class BeanPersistAdapter implements BeanPersistController {
public void postSoftDelete(BeanPersistRequest<?> request) {
}
/**
* Does nothing by default.
*/
@Override
public void preDelete(BeanDeleteIdRequest request) {
}
}
@@ -92,6 +92,11 @@ public interface BeanPersistController {
*/
boolean preSoftDelete(BeanPersistRequest<?> request);
/**
* Prior to a delete by id perform some action.
*/
void preDelete(BeanDeleteIdRequest request);
/**
* Called after the insert was performed.
*/
@@ -136,7 +136,6 @@ public class EJson {
/**
* Parse the json returning as a List taking into account the current token.
*/
@SuppressWarnings("unchecked")
public static <T> List<T> parseList(JsonParser parser, JsonToken currentToken) throws IOException {
return plugin.parseList(parser, currentToken);
}
@@ -34,13 +34,14 @@ public class AnnotationUtil {
* does not execute specialized search algorithms for classes or methods. It only traverses through Annotations!
* It also does not filter out platform dependent annotations!
*/
@SuppressWarnings("unchecked")
public static <A extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) {
if (annotationType == null) {
return null;
}
// check if directly present, if not, start search for meta-annotations.
Annotation[] anns = annotatedElement.getAnnotations();
if (anns.length == 0) {
Annotation[] anns = annotatedElement.getAnnotations();
if (anns.length == 0) {
return null; // no annotations present, so searching for meta annotations not required
}
@@ -51,7 +52,7 @@ public class AnnotationUtil {
return (A) ann;
}
}
return findAnnotation(anns, annotationType, new HashSet<>());
}
@@ -62,6 +63,7 @@ public class AnnotationUtil {
* the supplied element.
* <p><strong>Note</strong>: this method searches for annotations at class & superClass(es)!
*/
@SuppressWarnings("unchecked")
public static <A extends Annotation> A findAnnotationRecursive(Class<?> clazz, Class<A> annotationType) {
if (annotationType == null) {
return null;
@@ -1,10 +1,12 @@
package io.ebeaninternal.util;
package io.ebean.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* Utility for closing raw Jdbc resources.
@@ -13,6 +15,32 @@ public class JdbcClose {
private static final Logger logger = LoggerFactory.getLogger(JdbcClose.class);
/**
* Close the resultSet logging if an error occurs.
*/
public static void close(Statement statement) {
try {
if (statement != null) {
statement.close();
}
} catch (SQLException e) {
logger.warn("Error closing statement", e);
}
}
/**
* Close the resultSet logging if an error occurs.
*/
public static void close(ResultSet resultSet) {
try {
if (resultSet != null) {
resultSet.close();
}
} catch (SQLException e) {
logger.warn("Error closing resultSet", e);
}
}
/**
* Close the connection logging if an error occurs.
*/
@@ -89,6 +89,7 @@ public class ScopeTrans {
}
@Override
public String toString() {
return "ScopeTrans[" + transaction + "]";
}
@@ -25,6 +25,7 @@ public class ScopedTransaction extends SpiTransactionProxy {
this.manager = manager;
}
@Override
public String toString() {
return "ScopedTransaction[" + current + "]";
}
@@ -80,6 +80,11 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
*/
void clearQueryStatistics();
/**
* Return the transaction manager.
*/
SpiTransactionManager getTransactionManager();
/**
* Return all the descriptors.
*/
@@ -101,5 +101,5 @@ public interface SpiExpression extends Expression {
/**
* Check for match to a natural key query returning false if it doesn't match.
*/
boolean naturalKey(NaturalKeyQueryData data);
boolean naturalKey(NaturalKeyQueryData<?> data);
}
@@ -64,7 +64,7 @@ public interface SpiExpressionRequest {
/**
* Append a DB Like clause.
*/
void appendLike();
void appendLike(boolean rawLikeExpression);
/**
* Escapes a string to use it as exact match in Like clause.
@@ -1,7 +1,6 @@
package io.ebeaninternal.api;
import io.ebean.CacheMode;
import io.ebean.EbeanServer;
import io.ebean.ExpressionList;
import io.ebean.OrderBy;
import io.ebean.PersistenceContextScope;
@@ -10,10 +9,10 @@ import io.ebean.Query;
import io.ebean.bean.CallStack;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.bean.PersistenceContext;
import io.ebean.event.BeanQueryRequest;
import io.ebean.event.readaudit.ReadEvent;
import io.ebean.plugin.BeanType;
import io.ebeaninternal.server.autotune.ProfilingListener;
import io.ebeaninternal.server.core.SpiOrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.TableJoin;
@@ -338,7 +337,7 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
/**
* Return a copy of the query attaching to a different EbeanServer.
*/
SpiQuery<T> copy(EbeanServer server);
SpiQuery<T> copy(SpiEbeanServer server);
/**
* Return the type of query (List, Set, Map, Bean, rowCount etc).
@@ -567,7 +566,7 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
* The query plan excludes actual bind values (as they don't effect the query plan).
* </p>
*/
CQueryPlanKey prepare(BeanQueryRequest<?> request);
CQueryPlanKey prepare(SpiOrmQueryRequest<T> request);
/**
* Calculate a hash based on the bind values used in the query.
@@ -588,6 +587,16 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
*/
boolean isRawSql();
/**
* Return true if the query should have an order by appended automatically.
*/
boolean checkPagingOrderBy();
/**
* Return true if there is no Order By clause.
*/
boolean orderByIsEmpty();
/**
* Return the Order By clause or null if there is none defined.
*/
@@ -4,7 +4,23 @@ import io.ebean.SqlUpdate;
public interface SpiSqlUpdate extends SqlUpdate {
/**
* Return the Bind parameters.
*/
BindParams getBindParams();
/**
* Set the final sql being executed with named parameters replaced etc.
*/
void setGeneratedSql(String sql);
/**
* Return true if we are using getGeneratedKeys.
*/
boolean isGetGeneratedKeys();
/**
* Set the generated key value.
*/
void setGeneratedKey(Object idValue);
}
@@ -300,6 +300,11 @@ public interface SpiTransaction extends Transaction {
*/
void profileEvent(SpiProfileTransactionEvent event);
/**
* Set the profileStream to catch and time all the events for this transaction.
*/
void setProfileStream(ProfileStream profileStream);
/**
* Return the stream that profiling events are written to.
*/
@@ -0,0 +1,37 @@
package io.ebeaninternal.api;
import io.ebean.TxScope;
import javax.sql.DataSource;
/**
* Service provider interface for the transaction manager.
*/
public interface SpiTransactionManager {
/**
* Return the main DataSource.
*/
DataSource getDataSource();
/**
* Return the read only DataSource (if defined).
*/
DataSource getReadOnlyDataSource();
/**
* Return the currently active transaction (can be null).
*/
SpiTransaction getActive();
/**
* Push an externally managed transaction into scope (e.g. Spring managed transaction).
*/
ScopedTransaction externalBeginTransaction(SpiTransaction transaction, TxScope txScope);
/**
* Called when an externally managed transaction has completed.
*/
void externalRemoveTransaction();
}
@@ -60,6 +60,11 @@ abstract class SpiTransactionProxy implements SpiTransaction {
transaction.profileEvent(event);
}
@Override
public void setProfileStream(ProfileStream profileStream) {
transaction.setProfileStream(profileStream);
}
@Override
public ProfileStream profileStream() {
return transaction.profileStream();
@@ -183,6 +183,11 @@ public class DefaultDbMigration implements DbMigration {
platforms.add(new Pair(getPlatform(platform), prefix));
}
@Override
public void addDatabasePlatform(DatabasePlatform databasePlatform, String prefix) {
platforms.add(new Pair(databasePlatform, prefix));
}
/**
* Generate the next migration xml file and associated apply and rollback sql scripts.
* <p>
@@ -6,7 +6,9 @@ import io.ebeaninternal.dbmigration.ddlgeneration.platform.PlatformDdl;
import io.ebeaninternal.dbmigration.migration.AddColumn;
import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
import io.ebeaninternal.dbmigration.migration.AddTableComment;
import io.ebeaninternal.dbmigration.migration.AddUniqueConstraint;
import io.ebeaninternal.dbmigration.migration.AlterColumn;
import io.ebeaninternal.dbmigration.migration.AlterForeignKey;
import io.ebeaninternal.dbmigration.migration.ChangeSet;
import io.ebeaninternal.dbmigration.migration.CreateIndex;
import io.ebeaninternal.dbmigration.migration.CreateTable;
@@ -59,6 +61,10 @@ public class BaseDdlHandler implements DdlHandler {
generate(writer, (AddHistoryTable) change);
} else if (change instanceof DropHistoryTable) {
generate(writer, (DropHistoryTable) change);
} else if (change instanceof AddUniqueConstraint) {
generate(writer, (AddUniqueConstraint) change);
} else if (change instanceof AlterForeignKey) {
generate(writer, (AlterForeignKey) change);
} else {
throw new IllegalArgumentException("Unsupported change: " + change);
}
@@ -66,8 +72,13 @@ public class BaseDdlHandler implements DdlHandler {
}
@Override
public void generateExtra(DdlWrite write) throws IOException {
tableDdl.generateExtra(write);
public void generateProlog(DdlWrite write) throws IOException {
tableDdl.generateProlog(write);
}
@Override
public void generateEpilog(DdlWrite write) throws IOException {
tableDdl.generateEpilog(write);
}
@Override
@@ -120,4 +131,13 @@ public class BaseDdlHandler implements DdlHandler {
tableDdl.generate(writer, dropIndex);
}
@Override
public void generate(DdlWrite writer, AddUniqueConstraint constraint) throws IOException {
tableDdl.generate(writer, constraint);
}
@Override
public void generate(DdlWrite writer, AlterForeignKey alterForeignKey) throws IOException {
tableDdl.generate(writer, alterForeignKey);
}
}
@@ -3,7 +3,9 @@ package io.ebeaninternal.dbmigration.ddlgeneration;
import io.ebeaninternal.dbmigration.migration.AddColumn;
import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
import io.ebeaninternal.dbmigration.migration.AddTableComment;
import io.ebeaninternal.dbmigration.migration.AddUniqueConstraint;
import io.ebeaninternal.dbmigration.migration.AlterColumn;
import io.ebeaninternal.dbmigration.migration.AlterForeignKey;
import io.ebeaninternal.dbmigration.migration.ChangeSet;
import io.ebeaninternal.dbmigration.migration.CreateIndex;
import io.ebeaninternal.dbmigration.migration.CreateTable;
@@ -41,5 +43,11 @@ public interface DdlHandler {
void generate(DdlWrite writer, DropIndex dropIndex) throws IOException;
void generateExtra(DdlWrite write) throws IOException;
void generate(DdlWrite writer, AddUniqueConstraint constraint) throws IOException;
void generate(DdlWrite writer, AlterForeignKey alterForeignKey) throws IOException;
void generateProlog(DdlWrite write) throws IOException;
void generateEpilog(DdlWrite write) throws IOException;
}
@@ -3,7 +3,9 @@ package io.ebeaninternal.dbmigration.ddlgeneration;
import io.ebeaninternal.dbmigration.migration.AddColumn;
import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
import io.ebeaninternal.dbmigration.migration.AddTableComment;
import io.ebeaninternal.dbmigration.migration.AddUniqueConstraint;
import io.ebeaninternal.dbmigration.migration.AlterColumn;
import io.ebeaninternal.dbmigration.migration.AlterForeignKey;
import io.ebeaninternal.dbmigration.migration.CreateIndex;
import io.ebeaninternal.dbmigration.migration.CreateTable;
import io.ebeaninternal.dbmigration.migration.DropColumn;
@@ -68,8 +70,23 @@ public interface TableDdl {
*/
void generate(DdlWrite writer, DropIndex dropIndex) throws IOException;
/**
* Write add unique constraint.
*/
void generate(DdlWrite writer, AddUniqueConstraint constraint) throws IOException;
/**
* Writes alter foreign key statements.
*/
void generate(DdlWrite writer, AlterForeignKey alterForeignKey) throws IOException;
/**
* Generate any extra DDL such as stored procedures or TableValueParameters.
*/
void generateProlog(DdlWrite write) throws IOException;
/**
* Generate any extra DDL such as regeneration of history triggers.
*/
void generateExtra(DdlWrite write) throws IOException;
void generateEpilog(DdlWrite write) throws IOException;
}
@@ -13,7 +13,9 @@ import io.ebeaninternal.dbmigration.ddlgeneration.platform.util.IndexSet;
import io.ebeaninternal.dbmigration.migration.AddColumn;
import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
import io.ebeaninternal.dbmigration.migration.AddTableComment;
import io.ebeaninternal.dbmigration.migration.AddUniqueConstraint;
import io.ebeaninternal.dbmigration.migration.AlterColumn;
import io.ebeaninternal.dbmigration.migration.AlterForeignKey;
import io.ebeaninternal.dbmigration.migration.Column;
import io.ebeaninternal.dbmigration.migration.CreateIndex;
import io.ebeaninternal.dbmigration.migration.CreateTable;
@@ -330,7 +332,7 @@ public class BaseTableDdl implements TableDdl {
}
String[] columnNames = {col.getName()};
write.apply()
.append(platformDdl.alterTableAddUniqueConstraint(tableName, uqName, columnNames, Boolean.TRUE.equals(col.isNotnull())))
.append(platformDdl.alterTableAddUniqueConstraint(tableName, uqName, columnNames, Boolean.TRUE.equals(col.isNotnull()) ? null : columnNames))
.endOfStatement();
write.dropAllForeignKeys()
@@ -340,9 +342,10 @@ public class BaseTableDdl implements TableDdl {
for (UniqueConstraint constraint : externalCompoundUnique) {
String uqName = constraint.getName();
String[] columnNames = StringHelper.delimitedToArray(constraint.getColumnNames(), ",", false);
String[] columnNames = SplitColumns.split(constraint.getColumnNames());
String[] nullableColumns = SplitColumns.split(constraint.getNullableColumns());
write.apply()
.append(platformDdl.alterTableAddUniqueConstraint(tableName, uqName, columnNames, false)) // TODO: check if nullable
.append(platformDdl.alterTableAddUniqueConstraint(tableName, uqName, columnNames, nullableColumns))
.endOfStatement();
write.dropAllForeignKeys()
@@ -389,14 +392,7 @@ public class BaseTableDdl implements TableDdl {
protected void writeInlineForeignKey(DdlWrite write, Column column) throws IOException {
String references = column.getReferences();
int pos = references.lastIndexOf('.');
if (pos == -1) {
throw new IllegalStateException("Expecting period '.' character for table.column split but not found in [" + references + "]");
}
String refTableName = references.substring(0, pos);
String refColumnName = references.substring(pos + 1);
String fkConstraint = platformDdl.tableInlineForeignKey(new String[]{column.getName()}, refTableName, new String[]{refColumnName});
String fkConstraint = platformDdl.tableInlineForeignKey(new WriteForeignKey(null, column));
write.apply().append(",").newLine().append(" ").append(fkConstraint);
}
@@ -404,11 +400,7 @@ public class BaseTableDdl implements TableDdl {
List<ForeignKey> foreignKey = createTable.getForeignKey();
for (ForeignKey key : foreignKey) {
String refTableName = key.getRefTableName();
String[] cols = toColumnNamesSplit(key.getColumnNames());
String[] refColumns = toColumnNamesSplit(key.getRefColumnNames());
String fkConstraint = platformDdl.tableInlineForeignKey(cols, refTableName, refColumns);
String fkConstraint = platformDdl.tableInlineForeignKey(new WriteForeignKey(null, key));
write.apply().append(",").newLine().append(" ").append(fkConstraint);
}
}
@@ -433,61 +425,42 @@ public class BaseTableDdl implements TableDdl {
List<ForeignKey> foreignKey = createTable.getForeignKey();
for (ForeignKey key : foreignKey) {
String refTableName = key.getRefTableName();
String fkName = key.getName();
String[] cols = toColumnNamesSplit(key.getColumnNames());
String[] refColumns = toColumnNamesSplit(key.getRefColumnNames());
writeForeignKey(write, fkName, tableName, cols, refTableName, refColumns, key.getIndexName());
writeForeignKey(write, new WriteForeignKey(tableName, key));
}
}
protected void writeForeignKey(DdlWrite write, String tableName, Column column) throws IOException {
String fkName = column.getForeignKeyName();
String references = column.getReferences();
int pos = references.lastIndexOf('.');
if (pos == -1) {
throw new IllegalStateException("Expecting period '.' character for table.column split but not found in [" + references + "]");
}
String refTableName = references.substring(0, pos);
String refColumnName = references.substring(pos + 1);
String[] cols = {column.getName()};
String[] refCols = {refColumnName};
writeForeignKey(write, fkName, tableName, cols, refTableName, refCols, column.getForeignKeyIndex());
writeForeignKey(write, new WriteForeignKey(tableName, column));
}
protected void writeForeignKey(DdlWrite write, String fkName, String tableName, String[] columns, String refTable, String[] refColumns, String indexName) throws IOException {
protected void writeForeignKey(DdlWrite write, WriteForeignKey request) throws IOException {
tableName = lowerTableName(tableName);
String tableName = lowerTableName(request.table());
DdlBuffer fkeyBuffer = write.applyForeignKeys();
alterTableAddForeignKey(fkeyBuffer, fkName, tableName, columns, refTable, refColumns);
alterTableAddForeignKey(fkeyBuffer, request);
if (indexName != null) {
if (request.indexName() != null) {
// no matching unique constraint so add the index
fkeyBuffer.append(platformDdl.createIndex(indexName, tableName, columns)).endOfStatement();
fkeyBuffer.append(platformDdl.createIndex(request.indexName(), tableName, request.cols())).endOfStatement();
}
fkeyBuffer.end();
write.dropAllForeignKeys()
.append(platformDdl.alterTableDropForeignKey(tableName, fkName)).endOfStatement();
.append(platformDdl.alterTableDropForeignKey(tableName, request.fkName())).endOfStatement();
if (indexName != null) {
if (request.indexName() != null) {
write.dropAllForeignKeys()
.append(platformDdl.dropIndex(indexName, tableName)).endOfStatement();
.append(platformDdl.dropIndex(request.indexName(), tableName)).endOfStatement();
}
write.dropAllForeignKeys().end();
}
protected void alterTableAddForeignKey(DdlBuffer buffer, String fkName, String tableName, String[] columns, String refTable, String[] refColumns) throws IOException {
protected void alterTableAddForeignKey(DdlBuffer buffer, WriteForeignKey request) throws IOException {
String fkConstraint = platformDdl.alterTableAddForeignKey(tableName, fkName, columns, refTable, refColumns);
String fkConstraint = platformDdl.alterTableAddForeignKey(request);
if (fkConstraint != null && !fkConstraint.isEmpty()) {
buffer.append(fkConstraint).endOfStatement();
}
@@ -554,7 +527,7 @@ public class BaseTableDdl implements TableDdl {
for (UniqueConstraint uniqueConstraint : uniqueConstraints) {
if (inlineUniqueWhenNull) {
String uqName = uniqueConstraint.getName();
String[] columns = toColumnNamesSplit(uniqueConstraint.getColumnNames());
String[] columns = SplitColumns.split(uniqueConstraint.getColumnNames());
apply.append(",").newLine();
apply.append(" constraint ").append(uqName).append(" unique");
appendColumns(columns, apply);
@@ -571,17 +544,14 @@ public class BaseTableDdl implements TableDdl {
boolean inlineUniqueWhenNullable = platformDdl.isInlineUniqueWhenNullable();
List<Column> columns = createTable.getColumn();
List<Column> columns = new WriteUniqueConstraint(createTable.getColumn()).uniqueKeys();
for (Column column : columns) {
if (!Boolean.TRUE.equals(column.isPrimaryKey())
&& (hasValue(column.getUnique()) || hasValue(column.getUniqueOneToOne()))) {
if (Boolean.TRUE.equals(column.isNotnull()) || inlineUniqueWhenNullable) {
// normal mechanism for adding unique constraint
inlineUniqueConstraintSingle(apply, column);
} else {
// MsSqlServer & DB2 specific mechanism for adding unique constraints (that allow nulls)
externalUnique.add(column);
}
if (Boolean.TRUE.equals(column.isNotnull()) || inlineUniqueWhenNullable) {
// normal mechanism for adding unique constraint
inlineUniqueConstraintSingle(apply, column);
} else {
// SqlServer & DB2 specific mechanism for adding unique constraints (that allow nulls)
externalUnique.add(column);
}
}
}
@@ -625,13 +595,6 @@ public class BaseTableDdl implements TableDdl {
return cols;
}
/**
* Return as an array of string column names.
*/
protected String[] toColumnNamesSplit(String columns) {
return columns.split(",");
}
/**
* Convert the table lower case.
*/
@@ -662,7 +625,7 @@ public class BaseTableDdl implements TableDdl {
@Override
public void generate(DdlWrite writer, CreateIndex createIndex) throws IOException {
String[] cols = toColumnNamesSplit(createIndex.getColumns());
String[] cols = SplitColumns.split(createIndex.getColumns());
writer.apply()
.append(platformDdl.createIndex(createIndex.getIndexName(), createIndex.getTableName(), cols))
.endOfStatement();
@@ -679,6 +642,38 @@ public class BaseTableDdl implements TableDdl {
.append(platformDdl.dropIndex(dropIndex.getIndexName(), dropIndex.getTableName()))
.endOfStatement();
}
@Override
public void generate(DdlWrite writer, AddUniqueConstraint constraint) throws IOException {
if (DdlHelp.isDropConstraint(constraint.getColumnNames())) {
String ddl = platformDdl.alterTableDropUniqueConstraint(constraint.getTableName(), constraint.getConstraintName());
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
}
} else {
String[] cols = SplitColumns.split(constraint.getColumnNames());
String[] nullableColumns = SplitColumns.split(constraint.getNullableColumns());
String ddl = platformDdl.alterTableAddUniqueConstraint(constraint.getTableName(), constraint.getConstraintName(), cols, nullableColumns);
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
}
}
}
@Override
public void generate(DdlWrite writer, AlterForeignKey alterForeignKey) throws IOException {
if (DdlHelp.isDropForeignKey(alterForeignKey.getColumnNames())) {
String ddl = platformDdl.alterTableDropForeignKey(alterForeignKey.getTableName(), alterForeignKey.getName());
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
}
} else {
String ddl = platformDdl.alterTableAddForeignKey(new WriteForeignKey(alterForeignKey));
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
}
}
}
/**
* Add add history table DDL.
@@ -696,14 +691,20 @@ public class BaseTableDdl implements TableDdl {
platformDdl.dropHistoryTable(writer, dropHistoryTable);
}
@Override
public void generateProlog(DdlWrite write) throws IOException {
platformDdl.generateProlog(write);
}
/**
* Called at the end to generate additional ddl such as regenerate history triggers.
*/
@Override
public void generateExtra(DdlWrite write) throws IOException {
public void generateEpilog(DdlWrite write) throws IOException {
for (HistoryTableUpdate update : this.regenerateHistoryTriggers.values()) {
platformDdl.regenerateHistoryTriggers(write, update);
}
platformDdl.generateEpilog(write);
}
@Override
@@ -931,22 +932,9 @@ public class BaseTableDdl implements TableDdl {
}
}
protected void alterColumnAddForeignKey(DdlWrite writer, AlterColumn alterColumn) throws IOException {
String tableName = alterColumn.getTableName();
String fkName = alterColumn.getForeignKeyName();
String[] cols = {alterColumn.getColumnName()};
String references = alterColumn.getReferences();
int pos = references.lastIndexOf('.');
if (pos == -1) {
throw new IllegalStateException("Expecting period '.' character for table.column split but not found in [" + references + "]");
}
String refTableName = references.substring(0, pos);
String refColumnName = references.substring(pos + 1);
String[] refCols = {refColumnName};
alterTableAddForeignKey(writer.apply(), fkName, tableName, cols, refTableName, refCols);
alterTableAddForeignKey(writer.apply(), new WriteForeignKey(alterColumn));
}
protected void alterColumnDropForeignKey(DdlWrite writer, AlterColumn alter) throws IOException {
@@ -979,7 +967,7 @@ public class BaseTableDdl implements TableDdl {
String[] cols = {alter.getColumnName()};
boolean notNull = alter.isNotnull() != null ? alter.isNotnull() : Boolean.TRUE.equals(alter.isNotnull());
writer.apply()
.append(platformDdl.alterTableAddUniqueConstraint(alter.getTableName(), uqName, cols, notNull))
.append(platformDdl.alterTableAddUniqueConstraint(alter.getTableName(), uqName, cols, notNull ? null : cols))
.endOfStatement();
writer.dropAllForeignKeys()
@@ -1,5 +1,6 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.annotation.ConstraintMode;
import io.ebean.config.dbplatform.DatabasePlatform;
/**
@@ -18,13 +19,18 @@ public class DB2Ddl extends PlatformDdl {
}
@Override
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, boolean notNull) {
if (notNull) {
return super.alterTableAddUniqueConstraint(tableName, uqName, columns, true);
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 {
// Hmm: Complex workaround: https://www.ibm.com/developerworks/mydeveloperworks/blogs/SQLTips4DB2LUW/entry/unique_where_not_null_indexes26?lang=en
return "-- NOT SUPPORTED " + super.alterTableAddUniqueConstraint(tableName, uqName, columns, true);
return "-- NOT SUPPORTED " + super.alterTableAddUniqueConstraint(tableName, uqName, columns, nullableColumns);
}
}
@Override
protected void appendForeignKeyOnUpdate(StringBuilder buffer, ConstraintMode mode) {
// do nothing, no on update clause for db2
}
}
@@ -5,18 +5,35 @@ public class DdlHelp {
public static final String DROP_COMMENT = "DROP COMMENT";
/**
* Return true if the default value is the special DROP DEFAULT value.
*/
public static boolean isDropDefault(String defaultValue) {
return DROP_DEFAULT.equals(defaultValue);
}
public static final String DROP_CONSTRAINT = "DROP CONSTRAINT";
public static final String DROP_FOREIGN_KEY = "DROP FOREIGN KEY";
/**
* Return true if the default value is the special DROP DEFAULT value.
*/
public static boolean isDropComment(String comment) {
return DROP_COMMENT.equals(comment);
public static boolean isDropDefault(String value) {
return DROP_DEFAULT.equals(value);
}
/**
* Return true if the default value is the special DROP COMMENT value.
*/
public static boolean isDropComment(String value) {
return DROP_COMMENT.equals(value);
}
/**
* Return true if the default value is the special DROP CONSTRAINT value.
*/
public static boolean isDropConstraint(String value) {
return DROP_CONSTRAINT.equals(value);
}
/**
* Return true if the default value is the special DROP FOREIGN KEY value.
*/
public static boolean isDropForeignKey(String value) {
return DROP_FOREIGN_KEY.equals(value);
}
}
@@ -108,6 +108,15 @@ public class MySqlDdl extends PlatformDdl {
*/
@Override
public void addTableComment(DdlBuffer apply, String tableName, String tableComment) throws IOException {
if (DdlHelp.isDropComment(tableComment)) {
tableComment = "";
}
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
}
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.annotation.ConstraintMode;
import io.ebean.config.dbplatform.DatabasePlatform;
/**
@@ -14,11 +15,37 @@ public class Oracle10Ddl extends PlatformDdl {
this.dropConstraintIfExists = "drop constraint";
this.dropIndexIfExists = "drop index ";
this.dropTableCascade = " cascade constraints purge";
this.foreignKeyRestrict = "";
this.alterColumn = "modify";
this.columnSetNotnull = "not null";
this.columnSetNull = "null";
this.columnSetDefault = "default";
this.identitySuffix = " generated always as identity";
}
@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 {
// Hmm: https://stackoverflow.com/questions/11893134/oracle-create-unique-index-but-ignore-nulls
return "-- NOT YET IMPLEMENTED: " + super.alterTableAddUniqueConstraint(tableName, uqName, columns, nullableColumns);
}
}
@Override
protected void appendForeignKeyOnUpdate(StringBuilder buffer, ConstraintMode mode) {
// do nothing, no on update clause for oracle
}
@Override
protected void appendForeignKeyMode(StringBuilder buffer, String onMode, ConstraintMode mode) {
switch (mode) {
case SET_NULL:
case CASCADE:
super.appendForeignKeyMode(buffer, onMode, mode);
default:
// do nothing, defaults to RESTRICT effectively
}
}
}
@@ -1,11 +1,13 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.annotation.ConstraintMode;
import io.ebean.config.DbConstraintNaming;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbDefaultValue;
import io.ebean.config.dbplatform.DbIdentity;
import io.ebean.config.dbplatform.IdType;
import io.ebean.util.StringHelper;
import io.ebeaninternal.dbmigration.ddlgeneration.BaseDdlHandler;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlHandler;
@@ -17,7 +19,6 @@ 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 io.ebean.util.StringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -62,7 +63,8 @@ public class PlatformDdl {
*/
protected String dropSequenceIfExists = "drop sequence if exists ";
protected String foreignKeyRestrict = "on delete restrict on update restrict";
protected String foreignKeyOnDelete = "on delete";
protected String foreignKeyOnUpdate = "on update";
protected String identitySuffix = " auto_increment";
@@ -211,7 +213,7 @@ public class PlatformDdl {
buffer.append(" ");
buffer.append(lowerColumnName(column.getName()), 29);
buffer.append(platformType);
if (!Boolean.TRUE.equals(column.isPrimaryKey()) && !typeContainsDefault(platformType)) {
if (!Boolean.TRUE.equals(column.isPrimaryKey())) {
String defaultValue = convertDefaultValue(column.getDefaultValue());
if (defaultValue != null) {
buffer.append(" default ").append(defaultValue);
@@ -225,13 +227,6 @@ public class PlatformDdl {
// so that the database can potentially provide a nice SQL error
}
/**
* Return true if the type definition already contains a default value.
*/
private boolean typeContainsDefault(String platformType) {
return platformType.toLowerCase().contains(" default");
}
/**
* Convert the DB column default literal to platform specific.
*/
@@ -306,9 +301,7 @@ public class PlatformDdl {
if (initialValue > 1) {
sb.append(" start with ").append(initialValue);
}
if (allocationSize > 0 && allocationSize != 50) {
// at this stage ignoring allocationSize 50 as this is the 'default' and
// not consistent with the way Ebean batch fetches sequence values
if (allocationSize > 1) {
sb.append(" increment by ").append(allocationSize);
}
sb.append(";");
@@ -351,37 +344,68 @@ public class PlatformDdl {
/**
* Return the foreign key constraint when used inline with create table.
*/
public String tableInlineForeignKey(String[] columns, String refTable, String[] refColumns) {
public String tableInlineForeignKey(WriteForeignKey request) {
StringBuilder buffer = new StringBuilder(90);
buffer.append("foreign key");
appendColumns(columns, buffer);
buffer.append(" references ").append(lowerTableName(refTable));
appendColumns(refColumns, buffer);
appendWithSpace(foreignKeyRestrict, buffer);
appendColumns(request.cols(), buffer);
buffer.append(" references ").append(lowerTableName(request.refTable()));
appendColumns(request.refCols(), buffer);
appendForeignKeySuffix(request, buffer);
return buffer.toString();
}
/**
* Add foreign key.
*/
public String alterTableAddForeignKey(String tableName, String fkName, String[] columns, String refTable, String[] refColumns) {
public String alterTableAddForeignKey(WriteForeignKey request) {
StringBuilder buffer = new StringBuilder(90);
buffer
.append("alter table ").append(tableName)
.append(" add constraint ").append(fkName)
.append("alter table ").append(lowerTableName(request.table()))
.append(" add constraint ").append(request.fkName())
.append(" foreign key");
appendColumns(columns, buffer);
appendColumns(request.cols(), buffer);
buffer
.append(" references ")
.append(lowerTableName(refTable));
appendColumns(refColumns, buffer);
appendWithSpace(foreignKeyRestrict, buffer);
.append(lowerTableName(request.refTable()));
appendColumns(request.refCols(), buffer);
appendForeignKeySuffix(request, buffer);
return buffer.toString();
}
protected void appendForeignKeySuffix(WriteForeignKey request, StringBuilder buffer) {
appendForeignKeyOnDelete(buffer, withDefault(request.onDelete()));
appendForeignKeyOnUpdate(buffer, withDefault(request.onDelete()));
}
protected ConstraintMode withDefault(ConstraintMode mode) {
return (mode == null) ? ConstraintMode.RESTRICT : mode;
}
protected void appendForeignKeyOnDelete(StringBuilder buffer, ConstraintMode mode) {
appendForeignKeyMode(buffer, foreignKeyOnDelete, mode);
}
protected void appendForeignKeyOnUpdate(StringBuilder buffer, ConstraintMode mode) {
appendForeignKeyMode(buffer, foreignKeyOnUpdate, mode);
}
protected void appendForeignKeyMode(StringBuilder buffer, String onMode, ConstraintMode mode) {
buffer.append(" ").append(onMode).append(" ").append(translate(mode));
}
protected String translate(ConstraintMode mode) {
switch(mode) {
case SET_NULL: return "set null";
case SET_DEFAULT: return "set default";
case RESTRICT: return "restrict";
case CASCADE: return "cascade";
default:
throw new IllegalStateException("Unknown mode "+mode);
}
}
/**
* Drop a unique constraint from the table (Sometimes this is an index).
*/
@@ -401,7 +425,7 @@ public class PlatformDdl {
* <p>
* Overridden by MsSqlServer for specific null handling on unique constraints.
*/
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, boolean notNull) {
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, String[] nullableColumns) {
StringBuilder buffer = new StringBuilder(90);
buffer.append("alter table ").append(tableName).append(" add constraint ").append(uqName).append(" unique ");
@@ -418,17 +442,14 @@ public class PlatformDdl {
.append(" ").append(convertedType);
if (!onHistoryTable) {
if (isTrue(column.isNotnull())) {
buffer.append(" not null");
}
if (defaultValue != null) {
if (typeContainsDefault(convertedType)) {
logger.error("Cannot set default value for '" + tableName + "." + column.getName() + "'");
} else {
buffer.append(" default ");
buffer.append(defaultValue);
}
buffer.append(" default ");
buffer.append(defaultValue);
}
if (isTrue(column.isNotnull())) {
buffer.append(" not null");
}
buffer.endOfStatement();
@@ -586,4 +607,19 @@ public class PlatformDdl {
}
apply.append(String.format("comment on column %s.%s is '%s'", table, column, comment)).endOfStatement();
}
/**
* Use this to generate a prolog for each script (stored procedures)
*/
public void generateProlog(DdlWrite write) throws IOException {
}
/**
* Use this to generate an epilog. Will be added at the end of script
*/
public void generateEpilog(DdlWrite write) throws IOException {
}
}
@@ -3,8 +3,6 @@ package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
import java.io.IOException;
/**
* DB2 platform specific DDL.
*/
@@ -17,17 +15,17 @@ public class SQLiteDdl extends PlatformDdl {
}
@Override
public void addTableComment(DdlBuffer apply, String tableName, String tableComment) throws IOException {
public void addTableComment(DdlBuffer apply, String tableName, String tableComment) {
// not supported
}
@Override
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) throws IOException {
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) {
// not supported
}
@Override
public String alterTableAddForeignKey(String tableName, String fkName, String[] columns, String refTable, String[] refColumns) {
public String alterTableAddForeignKey(WriteForeignKey request) {
// not supported
return null;
}
@@ -0,0 +1,12 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
public class SplitColumns {
/**
* Return as an array of string column names.
*/
public static String[] split(String columns) {
return columns.split(",");
}
}
@@ -1,5 +1,6 @@
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.migration.AlterColumn;
@@ -14,7 +15,6 @@ public class SqlServerDdl extends PlatformDdl {
public SqlServerDdl(DatabasePlatform platform) {
super(platform);
this.identitySuffix = " identity(1,1)";
this.foreignKeyRestrict = "";
this.alterTableIfExists = "";
this.addColumn = "add";
this.inlineUniqueWhenNullable = false;
@@ -23,6 +23,13 @@ public class SqlServerDdl extends PlatformDdl {
this.historyDdl = new SqlServerHistoryDdl();
}
@Override
protected void appendForeignKeyMode(StringBuilder buffer, String onMode, ConstraintMode mode) {
if (mode != ConstraintMode.RESTRICT) {
super.appendForeignKeyMode(buffer, onMode, mode);
}
}
@Override
public String dropTable(String tableName) {
StringBuilder buffer = new StringBuilder();
@@ -56,9 +63,9 @@ public class SqlServerDdl extends PlatformDdl {
* MsSqlServer specific null handling on unique constraints.
*/
@Override
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, boolean notNull) {
if (notNull) {
return super.alterTableAddUniqueConstraint(tableName, uqName, columns, notNull);
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, String[] nullableColumns) {
if (nullableColumns == null || nullableColumns.length == 0) {
return super.alterTableAddUniqueConstraint(tableName, uqName, columns, nullableColumns);
}
if (uqName == null) {
throw new NullPointerException();
@@ -75,7 +82,7 @@ public class SqlServerDdl extends PlatformDdl {
}
sb.append(") where");
String sep = " ";
for (String column : columns) {
for (String column : nullableColumns) {
sb.append(sep).append(column).append(" is not null");
sep = " and ";
}
@@ -114,9 +121,7 @@ public class SqlServerDdl extends PlatformDdl {
} else {
sb.append(" start with 1 ");
}
if (allocationSize > 0 && allocationSize != 50) {
// at this stage ignoring allocationSize 50 as this is the 'default' and
// not consistent with the way Ebean batch fetches sequence values
if (allocationSize > 1) {
sb.append(" increment by ").append(allocationSize);
}
sb.append(";");
@@ -0,0 +1,112 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.annotation.ConstraintMode;
import io.ebeaninternal.dbmigration.migration.AlterColumn;
import io.ebeaninternal.dbmigration.migration.AlterForeignKey;
import io.ebeaninternal.dbmigration.migration.Column;
import io.ebeaninternal.dbmigration.migration.ForeignKey;
class WriteForeignKey {
private final String fkName;
private final String tableName;
private final String[] cols;
private String refTableName;
private String[] refCols;
private final String indexName;
private final ConstraintMode onDelete;
private final ConstraintMode onUpdate;
WriteForeignKey(AlterColumn alterColumn) {
this.tableName = alterColumn.getTableName();
this.indexName = alterColumn.getForeignKeyIndex();
this.fkName = alterColumn.getForeignKeyName();
this.cols = new String[]{alterColumn.getColumnName()};
setReferences(alterColumn.getReferences());
this.onDelete = modeOf(alterColumn.getForeignKeyOnDelete());
this.onUpdate = modeOf(alterColumn.getForeignKeyOnUpdate());
}
WriteForeignKey(String tableName, ForeignKey key) {
this.tableName = tableName;
this.indexName = key.getIndexName();
this.fkName = key.getName();
this.cols = toCols(key.getColumnNames());
this.refTableName = key.getRefTableName();
this.refCols = toCols(key.getRefColumnNames());
this.onDelete = modeOf(key.getOnDelete());
this.onUpdate = modeOf(key.getOnUpdate());
}
WriteForeignKey(AlterForeignKey key) {
this.tableName = key.getTableName();
this.indexName = key.getIndexName();
this.fkName = key.getName();
this.cols = toCols(key.getColumnNames());
this.refTableName = key.getRefTableName();
this.refCols = toCols(key.getRefColumnNames());
this.onDelete = modeOf(key.getOnDelete());
this.onUpdate = modeOf(key.getOnUpdate());
}
WriteForeignKey(String tableName, Column column) {
this.tableName = tableName;
this.indexName = column.getForeignKeyIndex();
this.fkName = column.getForeignKeyName();
this.cols = new String[]{column.getName()};
setReferences(column.getReferences());
this.onDelete = modeOf(column.getForeignKeyOnDelete());
this.onUpdate = modeOf(column.getForeignKeyOnUpdate());
}
private void setReferences(String references) {
int pos = references.lastIndexOf('.');
if (pos == -1) {
throw new IllegalStateException("Expecting period '.' character for table.column split but not found in [" + references + "]");
}
this.refTableName = references.substring(0, pos);
String refColumnName = references.substring(pos + 1);
this.refCols = new String[]{refColumnName};
}
private String[] toCols(String columns) {
return SplitColumns.split(columns);
}
private ConstraintMode modeOf(String value) {
return (value == null) ? null : ConstraintMode.valueOf(value);
}
public String table() {
return tableName;
}
public String[] cols() {
return cols;
}
public String indexName() {
return indexName;
}
public String fkName() {
return fkName;
}
public String refTable() {
return refTableName;
}
public String[] refCols() {
return refCols;
}
public ConstraintMode onDelete() {
return onDelete;
}
public ConstraintMode onUpdate() {
return onUpdate;
}
}
@@ -0,0 +1,47 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebeaninternal.dbmigration.migration.Column;
import java.util.ArrayList;
import java.util.List;
class WriteUniqueConstraint {
private final List<Column> primaryKeys = new ArrayList<>();
private final List<Column> uniqueKeys = new ArrayList<>();
WriteUniqueConstraint(List<Column> columns) {
// filter for unique and primary keys
for (Column column : columns) {
if (Boolean.TRUE.equals(column.isPrimaryKey())) {
primaryKeys.add(column);
}
if (hasValue(column.getUnique()) || hasValue(column.getUniqueOneToOne())){
uniqueKeys.add(column);
}
}
}
/**
* Return true if null or trimmed string is empty.
*/
boolean hasValue(String value) {
return value != null && !value.trim().isEmpty();
}
/**
* Return the single columns with unique constraints (that are not the primary key).
*/
public List<Column> uniqueKeys() {
if (uniqueKeys.isEmpty() || primaryKeys.size() > 1) {
// all single column unique constraints are valid
return uniqueKeys;
}
// filter out PFK
for (Column primaryKey : primaryKeys) {
uniqueKeys.remove(primaryKey);
}
return uniqueKeys;
}
}
@@ -0,0 +1,137 @@
package io.ebeaninternal.dbmigration.migration;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for anonymous complex type.
* <p>
* <p>The following schema fragment specifies the expected content contained within this class.
* <p>
* <pre>
* TODO
* </pre>
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "")
@XmlRootElement(name = "addUniqueConstraint")
public class AddUniqueConstraint {
@XmlAttribute(name = "constraintName", required = true)
protected String constraintName;
@XmlAttribute(name = "tableName", required = true)
protected String tableName;
@XmlAttribute(name = "columnNames", required = true)
protected String columnNames;
@XmlAttribute(name = "nullableColumns", required = true)
protected String nullableColumns;
@XmlAttribute(name = "oneToOne", required = false)
protected Boolean oneToOne;
/**
* Gets the value of the constraintName property.
*
* @return possible object is
* {@link String }
*/
public String getConstraintName() {
return constraintName;
}
/**
* Sets the value of the constraintName property.
*
* @param value allowed object is
* {@link String }
*/
public void setConstraintName(String value) {
this.constraintName = value;
}
/**
* Gets the value of the tableName property.
*
* @return possible object is
* {@link String }
*/
public String getTableName() {
return tableName;
}
/**
* Sets the value of the tableName property.
*
* @param value allowed object is
* {@link String }
*/
public void setTableName(String tableName) {
this.tableName = tableName;
}
/**
* Gets the value of the columnNames property.
*
* @return possible object is
* {@link String }
*/
public String getColumnNames() {
return columnNames;
}
/**
* Sets the value of the columnNames property.
*
* @param value allowed object is
* {@link String }
*/
public void setColumnNames(String value) {
this.columnNames = value;
}
/**
* Gets the value of the nullableColumns property.
*
* @return possible object is
* {@link String }
*/
public String getNullableColumns() {
return nullableColumns;
}
/**
* Sets the value of the nullableColumns property.
*
* @param value allowed object is
* {@link String }
*/
public void setNullableColumns(String value) {
this.nullableColumns = value;
}
/**
* Gets the value of the oneToOne property.
*
* @return true if oneToOne was set
*/
public boolean isOneToOne() {
return Boolean.TRUE.equals(oneToOne);
}
/**
* Sets the value of the oneToOne property.
*
* @param value boolean
*/
public void setOneToOne(boolean oneToOne) {
this.oneToOne = oneToOne;
}
}
@@ -1,14 +1,13 @@
package io.ebeaninternal.dbmigration.migration;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import java.util.ArrayList;
import java.util.List;
/**
@@ -40,6 +39,8 @@ import javax.xml.bind.annotation.XmlType;
* &lt;attribute name="references" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="foreignKeyName" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="foreignKeyIndex" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="foreignKeyOnDelete" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="foreignKeyOnUpdate" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="dropForeignKey" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="dropForeignKeyIndex" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;/restriction>
@@ -99,6 +100,10 @@ public class AlterColumn {
protected String foreignKeyName;
@XmlAttribute(name = "foreignKeyIndex")
protected String foreignKeyIndex;
@XmlAttribute(name = "foreignKeyOnDelete")
protected String foreignKeyOnDelete;
@XmlAttribute(name = "foreignKeyOnUpdate")
protected String foreignKeyOnUpdate;
@XmlAttribute(name = "dropForeignKey")
protected String dropForeignKey;
@XmlAttribute(name = "dropForeignKeyIndex")
@@ -504,6 +509,46 @@ public class AlterColumn {
this.foreignKeyIndex = value;
}
/**
* Gets the value of the foreignKeyOnDelete property.
*
* @return possible object is
* {@link String }
*/
public String getForeignKeyOnDelete() {
return foreignKeyOnDelete;
}
/**
* Sets the value of the foreignKeyOnDelete property.
*
* @param value allowed object is
* {@link String }
*/
public void setForeignKeyOnDelete(String value) {
this.foreignKeyOnDelete = value;
}
/**
* Gets the value of the foreignKeyOnUpdate property.
*
* @return possible object is
* {@link String }
*/
public String getForeignKeyOnUpdate() {
return foreignKeyOnUpdate;
}
/**
* Sets the value of the foreignKeyOnUpdate property.
*
* @param value allowed object is
* {@link String }
*/
public void setForeignKeyOnUpdate(String value) {
this.foreignKeyOnUpdate = value;
}
/**
* Gets the value of the dropForeignKey property.
*
@@ -0,0 +1,199 @@
package io.ebeaninternal.dbmigration.migration;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for anonymous complex type.
* <p>
* <p>The following schema fragment specifies the expected content contained within this class.
* <p>
* <pre>
* TODO
* </pre>
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "")
@XmlRootElement(name = "foreignKey")
public class AlterForeignKey {
@XmlAttribute(name = "name", required = true)
protected String name;
@XmlAttribute(name = "columnNames", required = true)
protected String columnNames;
@XmlAttribute(name = "refColumnNames", required = true)
protected String refColumnNames;
@XmlAttribute(name = "refTableName", required = true)
protected String refTableName;
@XmlAttribute(name = "indexName")
protected String indexName;
@XmlAttribute(name = "tableName", required = true)
protected String tableName;
@XmlAttribute(name = "onDelete")
protected String onDelete;
@XmlAttribute(name = "onUpdate")
protected String onUpdate;
/**
* Gets the value of the name property.
*
* @return possible object is
* {@link String }
*/
public String getName() {
return name;
}
/**
* Sets the value of the name property.
*
* @param value allowed object is
* {@link String }
*/
public void setName(String value) {
this.name = value;
}
/**
* Gets the value of the columnNames property.
*
* @return possible object is
* {@link String }
*/
public String getColumnNames() {
return columnNames;
}
/**
* Sets the value of the columnNames property.
*
* @param value allowed object is
* {@link String }
*/
public void setColumnNames(String value) {
this.columnNames = value;
}
/**
* Gets the value of the refColumnNames property.
*
* @return possible object is
* {@link String }
*/
public String getRefColumnNames() {
return refColumnNames;
}
/**
* Sets the value of the refColumnNames property.
*
* @param value allowed object is
* {@link String }
*/
public void setRefColumnNames(String value) {
this.refColumnNames = value;
}
/**
* Gets the value of the refTableName property.
*
* @return possible object is
* {@link String }
*/
public String getRefTableName() {
return refTableName;
}
/**
* Sets the value of the refTableName property.
*
* @param value allowed object is
* {@link String }
*/
public void setRefTableName(String value) {
this.refTableName = value;
}
/**
* Gets the value of the indexName property.
*
* @return possible object is
* {@link String }
*/
public String getIndexName() {
return indexName;
}
/**
* Sets the value of the indexName property.
*
* @param value allowed object is
* {@link String }
*/
public void setIndexName(String value) {
this.indexName = value;
}
/**
* Gets the value of the tableName property.
*
* @return possible object is
* {@link String }
*/
public String getTableName() {
return tableName;
}
/**
* Sets the value of the tableName property.
*
* @param value allowed object is
* {@link String }
*/
public void setTableName(String value) {
this.tableName = value;
}
/**
* Gets the value of the onDelete property.
*
* @return possible object is
* {@link String }
*/
public String getOnDelete() {
return onDelete;
}
/**
* Sets the value of the onDelete property.
*
* @param value allowed object is
* {@link String }
*/
public void setOnDelete(String value) {
this.onDelete = value;
}
/**
* Gets the value of the onUpdate property.
*
* @return possible object is
* {@link String }
*/
public String getOnUpdate() {
return onUpdate;
}
/**
* Sets the value of the onUpdate property.
*
* @param value allowed object is
* {@link String }
*/
public void setOnUpdate(String value) {
this.onUpdate = value;
}
}
@@ -57,7 +57,9 @@ public class ChangeSet {
@XmlElement(name = "alterColumn", type = AlterColumn.class),
@XmlElement(name = "renameColumn", type = RenameColumn.class),
@XmlElement(name = "createIndex", type = CreateIndex.class),
@XmlElement(name = "dropIndex", type = DropIndex.class)
@XmlElement(name = "dropIndex", type = DropIndex.class),
@XmlElement(name = "addUniqueConstraint", type = AddUniqueConstraint.class),
@XmlElement(name = "alterForeignKey", type = AlterForeignKey.class),
})
protected List<Object> changeSetChildren;
@XmlAttribute(name = "type", required = true)
@@ -1,14 +1,13 @@
package io.ebeaninternal.dbmigration.migration;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import java.util.ArrayList;
import java.util.List;
/**
@@ -34,6 +33,8 @@ import javax.xml.bind.annotation.XmlType;
* &lt;attribute name="references" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="foreignKeyName" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="foreignKeyIndex" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="foreignKeyOnDelete" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="foreignKeyOnUpdate" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="comment" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;/restriction>
* &lt;/complexContent>
@@ -80,6 +81,10 @@ public class Column {
protected String foreignKeyName;
@XmlAttribute(name = "foreignKeyIndex")
protected String foreignKeyIndex;
@XmlAttribute(name = "foreignKeyOnDelete")
protected String foreignKeyOnDelete;
@XmlAttribute(name = "foreignKeyOnUpdate")
protected String foreignKeyOnUpdate;
@XmlAttribute(name = "comment")
protected String comment;
@@ -378,6 +383,46 @@ public class Column {
this.foreignKeyIndex = value;
}
/**
* Gets the value of the foreignOnDelete property.
*
* @return possible object is
* {@link String }
*/
public String getForeignKeyOnDelete() {
return foreignKeyOnDelete;
}
/**
* Sets the value of the foreignKeyOnDelete property.
*
* @param value allowed object is
* {@link String }
*/
public void setForeignKeyOnDelete(String value) {
this.foreignKeyOnDelete = value;
}
/**
* Gets the value of the foreignOnUpdate property.
*
* @return possible object is
* {@link String }
*/
public String getForeignKeyOnUpdate() {
return foreignKeyOnUpdate;
}
/**
* Sets the value of the foreignKeyOnUpdate property.
*
* @param value allowed object is
* {@link String }
*/
public void setForeignKeyOnUpdate(String value) {
this.foreignKeyOnUpdate = value;
}
/**
* Gets the value of the comment property.
*
@@ -21,6 +21,8 @@ import javax.xml.bind.annotation.XmlType;
* &lt;attribute name="refColumnNames" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="refTableName" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="indexName" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="onDelete" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="onUpdate" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
@@ -41,7 +43,10 @@ public class ForeignKey {
protected String refTableName;
@XmlAttribute(name = "indexName")
protected String indexName;
@XmlAttribute(name = "onDelete")
protected String onDelete;
@XmlAttribute(name = "onUpdate")
protected String onUpdate;
/**
* Gets the value of the name property.
*
@@ -142,4 +147,44 @@ public class ForeignKey {
this.indexName = value;
}
/**
* Gets the value of the onDelete property.
*
* @return possible object is
* {@link String }
*/
public String getOnDelete() {
return onDelete;
}
/**
* Sets the value of the onDelete property.
*
* @param value allowed object is
* {@link String }
*/
public void setOnDelete(String value) {
this.onDelete = value;
}
/**
* Gets the value of the onUpdate property.
*
* @return possible object is
* {@link String }
*/
public String getOnUpdate() {
return onUpdate;
}
/**
* Sets the value of the onUpdate property.
*
* @param value allowed object is
* {@link String }
*/
public void setOnUpdate(String value) {
this.onUpdate = value;
}
}
@@ -194,5 +194,17 @@ public class ObjectFactory {
return new DdlScript();
}
/**
* Create an instance of {@link AddUniqueConstraint }
*/
public AddUniqueConstraint createAddUniqueConstraint() {
return new AddUniqueConstraint();
}
/**
* Create an instance of {@link AddUniqueConstraint }
*/
public AlterForeignKey createAlterForeignKey() {
return new AlterForeignKey();
}
}
@@ -30,9 +30,16 @@ public class UniqueConstraint {
@XmlAttribute(name = "name", required = true)
protected String name;
@XmlAttribute(name = "columnNames", required = true)
protected String columnNames;
@XmlAttribute(name = "oneToOne", required = false)
protected Boolean oneToOne;
@XmlAttribute(name = "nullableColumns", required = true)
protected String nullableColumns;
/**
* Gets the value of the name property.
*
@@ -72,5 +79,43 @@ public class UniqueConstraint {
public void setColumnNames(String value) {
this.columnNames = value;
}
/**
* Gets the value of the oneToOne property.
*
* @return true if oneToOne was set
*/
public boolean isOneToOne() {
return Boolean.TRUE.equals(oneToOne);
}
/**
* Sets the value of the oneToOne property.
*
* @param value boolean
*/
public void setOneToOne(boolean oneToOne) {
this.oneToOne = oneToOne;
}
/**
* Gets the value of the nullableColumns property.
*
* @return possible object is
* {@link String }
*/
public String getNullableColumns() {
return nullableColumns;
}
/**
* Sets the value of the nullableColumns property.
*
* @param value allowed object is
* {@link String }
*/
public void setNullableColumns(String value) {
this.nullableColumns = value;
}
}
@@ -107,6 +107,10 @@ public class CurrentModel {
createDdl();
StringBuilder ddl = new StringBuilder(2000);
String header = server.getServerConfig().getMigrationConfig().getDdlHeader();
if (header != null && !header.isEmpty()) {
ddl.append(header).append('\n');
}
ddl.append(write.apply().getBuffer());
ddl.append(write.applyForeignKeys().getBuffer());
ddl.append(write.applyHistory().getBuffer());
@@ -122,6 +126,10 @@ public class CurrentModel {
createDdl();
StringBuilder ddl = new StringBuilder(2000);
String header = server.getServerConfig().getMigrationConfig().getDdlHeader();
if (header != null && !header.isEmpty()) {
ddl.append(header).append('\n');
}
ddl.append(write.dropAllForeignKeys().getBuffer());
ddl.append(write.dropAll().getBuffer());
@@ -139,7 +147,9 @@ public class CurrentModel {
write = new DdlWrite(new MConfiguration(), model);
DdlHandler handler = handler();
handler.generateProlog(write);
handler.generate(write, createChangeSet);
handler.generateEpilog(write);
}
}
@@ -1,13 +1,14 @@
package io.ebeaninternal.dbmigration.model;
import java.util.List;
import io.ebean.annotation.ConstraintMode;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.DdlHelp;
import io.ebeaninternal.dbmigration.migration.AlterColumn;
import io.ebeaninternal.dbmigration.migration.Column;
import io.ebeaninternal.dbmigration.migration.DdlScript;
import io.ebeaninternal.server.deploy.DbMigrationInfo;
import java.util.List;
/**
* A column in the logical model.
*/
@@ -21,6 +22,8 @@ public class MColumn {
private String references;
private String foreignKeyName;
private String foreignKeyIndex;
private ConstraintMode fkeyOnDelete;
private ConstraintMode fkeyOnUpdate;
private String comment;
private boolean historyExclude;
@@ -55,6 +58,8 @@ public class MColumn {
this.references = column.getReferences();
this.foreignKeyName = column.getForeignKeyName();
this.foreignKeyIndex = column.getForeignKeyIndex();
this.fkeyOnDelete = fkeyMode(column.getForeignKeyOnDelete());
this.fkeyOnUpdate = fkeyMode(column.getForeignKeyOnUpdate());
this.notnull = Boolean.TRUE.equals(column.isNotnull());
this.primaryKey = Boolean.TRUE.equals(column.isPrimaryKey());
this.identity = Boolean.TRUE.equals(column.isIdentity());
@@ -63,6 +68,10 @@ public class MColumn {
this.historyExclude = Boolean.TRUE.equals(column.isHistoryExclude());
}
private ConstraintMode fkeyMode(String mode) {
return (mode == null) ? null : ConstraintMode.valueOf(mode);
}
public MColumn(String name, String type) {
this.name = name;
this.type = type;
@@ -89,6 +98,8 @@ public class MColumn {
copy.comment = comment;
copy.foreignKeyName = foreignKeyName;
copy.foreignKeyIndex = foreignKeyIndex;
copy.fkeyOnUpdate = fkeyOnUpdate;
copy.fkeyOnDelete = fkeyOnDelete;
copy.historyExclude = historyExclude;
copy.notnull = notnull;
copy.primaryKey = primaryKey;
@@ -154,6 +165,11 @@ public class MColumn {
this.foreignKeyIndex = foreignKeyIndex;
}
public void setForeignKeyModes(ConstraintMode onDelete, ConstraintMode onUpdate) {
this.fkeyOnDelete = onDelete;
this.fkeyOnUpdate = onUpdate;
}
public String getDefaultValue() {
return defaultValue;
}
@@ -196,7 +212,7 @@ public class MColumn {
/**
* Set unique specifically for OneToOne mapping.
* We need special DDL for this case for MsSqlServer.
* We need special DDL for this case for SqlServer.
*/
public void setUniqueOneToOne(String uniqueOneToOne) {
this.uniqueOneToOne = uniqueOneToOne;
@@ -260,6 +276,8 @@ public class MColumn {
c.setReferences(references);
c.setForeignKeyName(foreignKeyName);
c.setForeignKeyIndex(foreignKeyIndex);
c.setForeignKeyOnDelete(fkeyModeOf(fkeyOnDelete));
c.setForeignKeyOnUpdate(fkeyModeOf(fkeyOnUpdate));
c.setDefaultValue(defaultValue);
c.setComment(comment);
c.setUnique(unique);
@@ -286,6 +304,10 @@ public class MColumn {
return c;
}
private String fkeyModeOf(ConstraintMode mode) {
return (mode == null) ? null : mode.name();
}
protected static boolean different(String val1, String val2) {
return (val1 == null) ? val2 != null : !val1.equals(val2);
}
@@ -382,7 +404,9 @@ public class MColumn {
alter.setCheckConstraint(newColumn.checkConstraint);
}
}
if (different(references, newColumn.references)) {
if (different(references, newColumn.references)
|| hasValue(newColumn.references) && fkeyOnDelete != newColumn.fkeyOnDelete
|| hasValue(newColumn.references) && fkeyOnUpdate != newColumn.fkeyOnUpdate) {
// foreign key change
AlterColumn alter = getAlterColumn(tableName, tableWithHistory);
if (hasValue(foreignKeyName)) {
@@ -396,6 +420,12 @@ public class MColumn {
alter.setReferences(newColumn.references);
alter.setForeignKeyName(newColumn.foreignKeyName);
alter.setForeignKeyIndex(newColumn.foreignKeyIndex);
if (newColumn.fkeyOnDelete != null) {
alter.setForeignKeyOnDelete(fkeyModeOf(newColumn.fkeyOnDelete));
}
if (newColumn.fkeyOnUpdate != null) {
alter.setForeignKeyOnUpdate(fkeyModeOf(newColumn.fkeyOnUpdate));
}
}
}
@@ -491,6 +521,11 @@ public class MColumn {
comment = null;
}
}
if (hasValue(alterColumn.getForeignKeyOnDelete())) {
fkeyOnDelete = fkeyMode(alterColumn.getForeignKeyOnDelete());
}
if (hasValue(alterColumn.getForeignKeyOnUpdate())) {
fkeyOnUpdate = fkeyMode(alterColumn.getForeignKeyOnUpdate());
}
}
}
@@ -1,9 +1,12 @@
package io.ebeaninternal.dbmigration.model;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.DdlHelp;
import io.ebeaninternal.dbmigration.migration.AlterForeignKey;
import io.ebeaninternal.dbmigration.migration.ForeignKey;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* A unique constraint for multiple columns.
@@ -46,6 +49,32 @@ public class MCompoundForeignKey {
fk.setRefTableName(referenceTable);
return fk;
}
/**
* Create and return an AlterForeignKey migration element.
*/
public AlterForeignKey addForeignKey(String tableName) {
AlterForeignKey fk = new AlterForeignKey();
fk.setName(name);
fk.setIndexName(indexName);
fk.setColumnNames(toColumnNames(columns));
fk.setRefColumnNames(toColumnNames(referenceColumns));
fk.setRefTableName(referenceTable);
fk.setTableName(tableName);
return fk;
}
/**
* Create and return an AlterForeignKey migration element.
*/
public AlterForeignKey dropForeignKey(String tableName) {
AlterForeignKey fk = new AlterForeignKey();
fk.setName(name);
fk.setIndexName(indexName);
fk.setColumnNames(DdlHelp.DROP_FOREIGN_KEY);
fk.setTableName(tableName);
return fk;
}
/**
* Add a counter to the foreign key and index names to avoid duplication.
@@ -99,4 +128,24 @@ public class MCompoundForeignKey {
return sb.toString();
}
@Override
public int hashCode() {
return Objects.hash(columns, indexName, name, referenceColumns, referenceTable);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof MCompoundForeignKey))
return false;
MCompoundForeignKey other = (MCompoundForeignKey) obj;
return Objects.equals(columns, other.columns)
&& Objects.equals(indexName, other.indexName)
&& Objects.equals(name, other.name)
&& Objects.equals(referenceColumns, other.referenceColumns)
&& Objects.equals(referenceTable, other.referenceTable);
}
}
@@ -1,5 +1,11 @@
package io.ebeaninternal.dbmigration.model;
import java.util.Arrays;
import java.util.Objects;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.DdlHelp;
import io.ebeaninternal.dbmigration.migration.AddUniqueConstraint;
import io.ebeaninternal.dbmigration.migration.UniqueConstraint;
/**
* A unique constraint for multiple columns.
* <p>
@@ -20,6 +26,8 @@ public class MCompoundUniqueConstraint {
* The columns combined to be unique.
*/
private final String[] columns;
private String[] nullableColumns;
public MCompoundUniqueConstraint(String[] columns, boolean oneToOne, String name) {
this.name = name;
@@ -47,4 +55,78 @@ public class MCompoundUniqueConstraint {
public String getName() {
return name;
}
public UniqueConstraint getUniqueConstraint() {
UniqueConstraint uq = new UniqueConstraint();
uq.setName(getName());
uq.setColumnNames(join(columns));
uq.setNullableColumns(join(nullableColumns));
uq.setOneToOne(isOneToOne());
return uq;
}
/**
* Return a AddUniqueConstraint migration for this constraint.
*/
public AddUniqueConstraint addUniqueConstraint(String tableName) {
AddUniqueConstraint create = new AddUniqueConstraint();
create.setConstraintName(getName());
create.setTableName(tableName);
create.setColumnNames(join(columns));
create.setNullableColumns(join(nullableColumns));
create.setOneToOne(isOneToOne());
return create;
}
/**
* Create a AddUniqueConstraint migration with 'DROP CONSTRAINT' set for this index.
*/
public AddUniqueConstraint dropUniqueConstraint(String tableName) {
AddUniqueConstraint dropUniqueConstraint = new AddUniqueConstraint();
dropUniqueConstraint.setConstraintName(name);
dropUniqueConstraint.setTableName(tableName);
dropUniqueConstraint.setColumnNames(DdlHelp.DROP_CONSTRAINT);
dropUniqueConstraint.setNullableColumns(join(nullableColumns));
return dropUniqueConstraint;
}
public void setNullableColumns(String[] nullableColumns) {
if (nullableColumns != null && nullableColumns.length == 0) {
this.nullableColumns = null;
} else {
this.nullableColumns = nullableColumns;
}
}
private String join(String[] arr) {
if (arr == null) {
return "";
}
StringBuilder sb = new StringBuilder(50);
for (int i = 0; i < arr.length; i++) {
if (i > 0) {
sb.append(",");
}
sb.append(arr[i]);
}
return sb.toString();
}
@Override
public int hashCode() {
return Arrays.hashCode(columns) + 31 * Objects.hash(name, oneToOne);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof MCompoundUniqueConstraint)) {
return false;
}
MCompoundUniqueConstraint other = (MCompoundUniqueConstraint) obj;
return Arrays.equals(columns, other.columns)
&& Arrays.equals(nullableColumns, other.nullableColumns)
&& Objects.equals(name, other.name)
&& oneToOne == other.oneToOne;
}
}
@@ -1,6 +1,7 @@
package io.ebeaninternal.dbmigration.model;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.DdlHelp;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.SplitColumns;
import io.ebeaninternal.dbmigration.migration.AddColumn;
import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
import io.ebeaninternal.dbmigration.migration.AddTableComment;
@@ -10,6 +11,7 @@ import io.ebeaninternal.dbmigration.migration.CreateTable;
import io.ebeaninternal.dbmigration.migration.DropColumn;
import io.ebeaninternal.dbmigration.migration.DropHistoryTable;
import io.ebeaninternal.dbmigration.migration.DropTable;
import io.ebeaninternal.dbmigration.migration.ForeignKey;
import io.ebeaninternal.dbmigration.migration.IdentityType;
import io.ebeaninternal.dbmigration.migration.UniqueConstraint;
import org.slf4j.Logger;
@@ -165,9 +167,33 @@ public class MTable {
for (Column column : cols) {
addColumn(column);
}
List<UniqueConstraint> uqConstraints = createTable.getUniqueConstraint();
for (UniqueConstraint uq : uqConstraints) {
MCompoundUniqueConstraint mUq = new MCompoundUniqueConstraint(SplitColumns.split(uq.getColumnNames()), uq.isOneToOne(), uq.getName());
mUq.setNullableColumns(SplitColumns.split(uq.getNullableColumns()));
uniqueConstraints.add(mUq);
}
for (ForeignKey fk : createTable.getForeignKey()) {
if (DdlHelp.isDropForeignKey(fk.getColumnNames())) {
removeForeignKey(fk.getName());
} else {
addForeignKey(fk.getName(), fk.getRefTableName(), fk.getIndexName(), fk.getColumnNames(), fk.getRefColumnNames());
}
}
}
public void addForeignKey(String name, String refTableName, String indexName, String columnNames, String refColumnNames) {
MCompoundForeignKey foreignKey = new MCompoundForeignKey(name, refTableName, indexName);
String[] cols = SplitColumns.split(columnNames);
String[] refCols = SplitColumns.split(refColumnNames);
for (int i = 0; i < cols.length && i < refCols.length; i++) {
foreignKey.addColumnPair(cols[i], refCols[i]);
}
addForeignKey(foreignKey);
}
/**
* Construct typically from EbeanServer meta data.
*/
@@ -236,17 +262,7 @@ public class MTable {
}
for (MCompoundUniqueConstraint constraint : uniqueConstraints) {
UniqueConstraint uq = new UniqueConstraint();
uq.setName(constraint.getName());
String[] columns = constraint.getColumns();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < columns.length; i++) {
if (i > 0) {
sb.append(",");
}
sb.append(columns[i]);
}
uq.setColumnNames(sb.toString());
UniqueConstraint uq = constraint.getUniqueConstraint();
createTable.getUniqueConstraint().add(uq);
}
@@ -271,6 +287,25 @@ public class MTable {
}
}
compareColumns(modelDiff, newTable);
if (MColumn.different(comment, newTable.comment)) {
AddTableComment addTableComment = new AddTableComment();
addTableComment.setName(name);
if (newTable.comment == null) {
addTableComment.setComment(DdlHelp.DROP_COMMENT);
} else {
addTableComment.setComment(newTable.comment);
}
modelDiff.addTableComment(addTableComment);
}
compareCompoundKeys(modelDiff, newTable);
compareUniqueKeys(modelDiff, newTable);
}
private void compareColumns(ModelDiff modelDiff, MTable newTable) {
addColumn = null;
Map<String, MColumn> newColumnMap = newTable.getColumns();
@@ -303,16 +338,39 @@ public class MTable {
if (addColumn != null) {
modelDiff.addAddColumn(addColumn);
}
}
if (MColumn.different(comment, newTable.comment)) {
AddTableComment addTableComment = new AddTableComment();
addTableComment.setName(name);
if (newTable.comment == null) {
addTableComment.setComment(DdlHelp.DROP_COMMENT);
} else {
addTableComment.setComment(newTable.comment);
}
modelDiff.addTableComment(addTableComment);
private void compareCompoundKeys(ModelDiff modelDiff, MTable newTable) {
List<MCompoundForeignKey> newKeys = new ArrayList<>(newTable.getCompoundKeys());
List<MCompoundForeignKey> currentKeys = new ArrayList<>(getCompoundKeys());
// remove keys that have not changed
currentKeys.removeAll(newTable.getCompoundKeys());
newKeys.removeAll(getCompoundKeys());
for (MCompoundForeignKey currentKey : currentKeys) {
modelDiff.addAlterForeignKey(currentKey.dropForeignKey(name));
}
for (MCompoundForeignKey newKey : newKeys) {
modelDiff.addAlterForeignKey(newKey.addForeignKey(name));
}
}
private void compareUniqueKeys(ModelDiff modelDiff, MTable newTable) {
List<MCompoundUniqueConstraint> newKeys = new ArrayList<>(newTable.getUniqueConstraints());
List<MCompoundUniqueConstraint> currentKeys = new ArrayList<>(getUniqueConstraints());
// remove keys that have not changed
currentKeys.removeAll(newTable.getUniqueConstraints());
newKeys.removeAll(getUniqueConstraints());
for (MCompoundUniqueConstraint currentKey: currentKeys) {
modelDiff.addUniqueConstraint(currentKey.dropUniqueConstraint(name));
}
for (MCompoundUniqueConstraint newKey: newKeys) {
modelDiff.addUniqueConstraint(newKey.addUniqueConstraint(name));
}
}
@@ -408,7 +466,6 @@ public class MTable {
* Return all the columns (excluding columns marked as dropped).
*/
public Collection<MColumn> allColumns() {
return columns.values();
}
@@ -657,4 +714,35 @@ public class MTable {
return draftTableName + "." + references.substring(lastDot + 1);
}
/**
* This method adds information which columns are nullable or not to the compound indices.
*/
public void updateCompoundIndices() {
for (MCompoundUniqueConstraint uniq : uniqueConstraints) {
List<String> nullableColumns = new ArrayList<>();
for (String columnName : uniq.getColumns()) {
MColumn col = getColumn(columnName);
if (col == null) {
throw new IllegalStateException("Column '" + columnName + "' not found in table " + getName());
}
if (!col.isNotnull()) {
nullableColumns.add(columnName);
}
}
uniq.setNullableColumns(nullableColumns.toArray(new String[nullableColumns.size()]));
}
}
public void removeForeignKey(String name) {
compoundKeys.removeIf(fk -> fk.getName().equals(name));
}
/**
* Clear the indexes on the foreign keys as they are covered by unique constraints.
*/
public void clearForeignKeyIndexes() {
for (MCompoundForeignKey compoundKey : compoundKeys) {
compoundKey.setIndexName(null);
}
}
}
@@ -1,10 +1,13 @@
package io.ebeaninternal.dbmigration.model;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.DdlHelp;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.SplitColumns;
import io.ebeaninternal.dbmigration.migration.AddColumn;
import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
import io.ebeaninternal.dbmigration.migration.AddTableComment;
import io.ebeaninternal.dbmigration.migration.AddUniqueConstraint;
import io.ebeaninternal.dbmigration.migration.AlterColumn;
import io.ebeaninternal.dbmigration.migration.AlterForeignKey;
import io.ebeaninternal.dbmigration.migration.ChangeSet;
import io.ebeaninternal.dbmigration.migration.ChangeSetType;
import io.ebeaninternal.dbmigration.migration.CreateIndex;
@@ -134,6 +137,10 @@ public class ModelContainer {
applyChange((AddHistoryTable) change);
} else if (change instanceof DropHistoryTable) {
applyChange((DropHistoryTable) change);
} else if (change instanceof AddUniqueConstraint) {
applyChange((AddUniqueConstraint) change);
} else if (change instanceof AlterForeignKey) {
applyChange((AlterForeignKey) change);
} else if (change instanceof AddTableComment) {
applyChange((AddTableComment) change);
} else {
@@ -166,6 +173,34 @@ public class ModelContainer {
table.setWithHistory(false);
}
private void applyChange(AddUniqueConstraint change) {
MTable table = tables.get(change.getTableName());
if (table == null) {
throw new IllegalStateException("Table [" + change.getTableName() + "] does not exist in model?");
}
if (DdlHelp.isDropConstraint(change.getColumnNames())) {
table.getUniqueConstraints().removeIf(constraint -> constraint.getName().equals(change.getConstraintName()));
} else {
MCompoundUniqueConstraint constraint = new MCompoundUniqueConstraint(
SplitColumns.split(change.getColumnNames()), change.isOneToOne(), change.getConstraintName());
constraint.setNullableColumns(SplitColumns.split(change.getNullableColumns()));
table.getUniqueConstraints().add(constraint);
}
}
private void applyChange(AlterForeignKey change) {
MTable table = tables.get(change.getTableName());
if (table == null) {
throw new IllegalStateException("Table [" + change.getName() + "] does not exist in model?");
}
if (DdlHelp.isDropForeignKey(change.getColumnNames())) {
table.removeForeignKey(change.getName());
} else {
table.addForeignKey(change.getName(), change.getRefTableName(), change.getIndexName(), change.getColumnNames(),
change.getRefColumnNames());
}
}
private void applyChange(AddTableComment change) {
MTable table = tables.get(change.getName());
if (table == null) {
@@ -3,7 +3,9 @@ package io.ebeaninternal.dbmigration.model;
import io.ebeaninternal.dbmigration.migration.AddColumn;
import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
import io.ebeaninternal.dbmigration.migration.AddTableComment;
import io.ebeaninternal.dbmigration.migration.AddUniqueConstraint;
import io.ebeaninternal.dbmigration.migration.AlterColumn;
import io.ebeaninternal.dbmigration.migration.AlterForeignKey;
import io.ebeaninternal.dbmigration.migration.ChangeSet;
import io.ebeaninternal.dbmigration.migration.ChangeSetType;
import io.ebeaninternal.dbmigration.migration.CreateIndex;
@@ -245,4 +247,18 @@ public class ModelDiff {
public void addTableComment(AddTableComment addTableComment) {
applyChanges.add(addTableComment);
}
/**
* Adds (or drops) a unique constraint to the 'apply' changes.
*/
public void addUniqueConstraint(AddUniqueConstraint addUniqueConstraint) {
applyChanges.add(addUniqueConstraint);
}
/**
* Adds (or drops) a foreign key constraint to the 'apply' changes.
*/
public void addAlterForeignKey(AlterForeignKey alterForeignKey) {
applyChanges.add(alterForeignKey);
}
}
@@ -45,14 +45,14 @@ public class PlatformDdlWriter {
public void processMigration(Migration dbMigration, DdlWrite write, File writePath, String fullVersion) throws IOException {
DdlHandler handler = handler();
handler.generateProlog(write);
List<ChangeSet> changeSets = dbMigration.getChangeSet();
for (ChangeSet changeSet : changeSets) {
if (isApply(changeSet)) {
handler.generate(write, changeSet);
}
}
handler.generateExtra(write);
handler.generateEpilog(write);
writePlatformDdl(write, writePath, fullVersion);
}
@@ -88,6 +88,10 @@ public class PlatformDdlWriter {
*/
protected void writeApplyDdl(Writer writer, DdlWrite write) throws IOException {
String header = config.getDdlHeader();
if (header != null) {
writer.append(header).append('\n');
}
// merge the apply buffers in the appropriate order
prependDropDependencies(writer, write.applyDropDependencies());
writer.append("-- apply changes\n");

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