Compare commits

..
Author SHA1 Message Date
Rob Bygrave 4d8bc0a2ce [maven-release-plugin] prepare release ebean-11.14.2 2018-03-07 20:22:25 +13:00
Rob Bygrave e182190f96 #1339 - Regression: Fix for not throwing OptimisticLockException when loaded bean has @Version property set 2018-03-07 20:19:48 +13:00
Rob Bygrave 8711ebc6f7 Merge branch 'fix-limit-constraint-names' of https://github.com/FOCONIS/ebean into FOCONIS-fix-limit-constraint-names 2018-03-07 17:13:56 +13:00
Rob Bygrave baa7242939 #1338 - Regression since 11.12.1 - resolve expressions in Properties given to serverConfig.loadFromProperties() 2018-03-07 16:43:42 +13:00
Rob Bygrave 47e8d27212 #1337 - Fix YamlLoader ... such that it loads multi-part yml source 2018-03-07 16:21:00 +13:00
Rob Bygrave 70aed3906e [maven-release-plugin] prepare for next development iteration 2018-03-07 02:24:21 +13:00
Rob Bygrave 70c3860a65 [maven-release-plugin] prepare release ebean-11.14.1 2018-03-07 02:24:07 +13:00
Rob Bygrave 1ba21d5c43 Bump to 11.14.1-SNAPSHOT ... Modified DDL generation order of create index vs alter table add FK 2018-03-07 02:22:58 +13:00
Rob Bygrave 2019bc7bf1 #1336 - Change DDL generation such that it does create index before alter table add foreign key 2018-03-07 02:19:10 +13:00
Rob Bygrave 3cb52917e3 #1335 - Add Try catch to YAML loading ... add system property ebeanSkipYaml ... to enable skipping the read of application.yml (well, all yml) 2018-03-07 02:03:54 +13:00
Rob Bygrave 55a257a64c #1334 - ENH: Convert ORM query into DTO query ... with dynamic formula support 2018-03-07 01:52:18 +13:00
Rob Bygrave 83ef204f3c Merge branch 'master' of github.com:ebean-orm/ebean 2018-03-06 21:15:07 +13:00
Rob Bygrave a18cac035b #1333 - ENH: Support "dynamic formula" with findSingleAttributeList() ... e.g. .select("concat(lastName,', ',firstName)")
Support property alias via "AS" ...  concat(lastName,', ',firstName) as lastName
2018-03-06 21:14:26 +13:00
Rob Bygrave 873c14faaf #1333 - ENH: Support "dynamic formula" with findSingleAttributeList() ... e.g. .select("concat(lastName,', ',firstName)")
Support cast of formula like ... select("max(updtime)::Instant")
2018-03-06 20:58:30 +13:00
Rob Bygrave e1af7c29eb #1333 - ENH: Support "dynamic formula" with findSingleAttributeList() ... e.g. .select("concat(lastName,', ',firstName)")
Concat automatically maps to String ScalarType
2018-03-06 17:35:26 +13:00
Rob Bygrave c1c85b67c2 #1333 - ENH: Support "dynamic formula" with findSingleAttributeList() ... e.g. .select("concat(lastName,', ',firstName)") 2018-03-06 17:29:45 +13:00
Rob Bygrave 05b8adbf6e No effective change - change test from extends testcase to normal junit 2018-03-06 16:37:47 +13:00
Rob Bygrave 3ca4efc818 #1331 - ENH: Support Aggregation formula with findSingleAttribute() ... max, min, avg, count and count(distinct ..) 2018-03-06 12:58:43 +13:00
Rob Bygrave ec2c3b13d4 No effective change - add test for deploy parser 2018-03-06 01:08:42 +13:00
Rob Bygrave 5a3e386b7c No effective change - add test for selecting only some @Aggregation properties 2018-03-05 19:59:40 +13:00
Roland PramlandRob Bygrave 7e56a147be FIX: ReadOnlyDatasource must also be configured (#1323) 2018-03-05 19:29:40 +13:00
Rob Bygrave 62cdfb3209 #1330 - Update ANTLR dependency to 4.7.1 2018-03-05 19:22:38 +13:00
Rob Bygrave 740532da4b #1329 - Ebean 11.12.1 broken when using ScalarTypeConverter with enum 2018-03-05 19:19:27 +13:00
Rob Bygrave 130356cb3b [maven-release-plugin] prepare for next development iteration 2018-03-02 23:39:17 +13:00
Rob Bygrave ab84cfae52 [maven-release-plugin] prepare release ebean-11.13.1 2018-03-02 23:39:03 +13:00
Rob Bygrave ad21b7a3fd Merge branch 'master' of github.com:ebean-orm/ebean 2018-03-02 23:37:55 +13:00
Rob Bygrave 31581082d2 Bump to 11.13.1-SNAPSHOT ... added findDto() 2018-03-02 23:37:38 +13:00
Roland PramlandRob Bygrave aa84dfe7d8 Add: Configuration md that describes the ebean options (#1325) 2018-03-02 23:33:10 +13:00
Rob Bygrave aad5b33db2 #1297 - ENH: Add DtoQuery ... such that we can map native SQL queries automatically into "DTO beans" 2018-03-02 23:32:01 +13:00
Roland PramlandRob Bygrave 5801f7620d FIX: add columnDefaultValue to more places (#1327) 2018-03-02 16:39:53 +13:00
Roland PramlandRob Bygrave 514416a1e0 Testcase Fix for SqlServer (#1326)
Test change only - Testcase Fix for SqlServer
2018-03-02 16:38:59 +13:00
Roland PramlandRob Bygrave 8faa95adc1 ENH: new method AnnotationUtil.findMetaAnnotationsRecursive (#1319)
* ENH: new method AnnotationUtil.findMetaAnnotationsRecursive

* clarified in javadoc
2018-03-02 16:28:41 +13:00
Rob Bygrave ebaeafc11b #1303 - NEW: Ebean.checkUniqueness ... add example to javadoc 2018-03-02 16:25:13 +13:00
Rob Bygrave 98e014f8c8 #1300 - NEW: FetchCountDistinct ... add example to javadoc + support chaining use via ExpressionList 2018-03-02 14:30:38 +13:00
Rob Bygrave 78912d75e0 #1306 - No effective change - javadoc update 2018-03-02 13:45:31 +13:00
Rob Bygrave 09786a7062 #1301 - No effective change - format lines 2018-03-02 13:30:40 +13:00
Rob Bygrave 54530248a5 #1312 - ENH: added SqlRow factory method to RawSqlService
Support the PR added binaryOptimizedUUID
2018-03-02 13:25:55 +13:00
Roland Praml fdafa776e1 Merge remote-tracking branch 'ebean/master' into fix-limit-constraint-names
# Conflicts:
#	src/test/resources/dbmigration/migrationtest/db2/1.0__initial.sql
#	src/test/resources/dbmigration/migrationtest/db2/1.1.sql
#	src/test/resources/dbmigration/migrationtest/db2/1.3.sql
2018-03-02 01:03:36 +01:00
Roland Praml 392eb2744d ADD: comment 2018-03-02 00:17:23 +01:00
Roland PramlandRob Bygrave b642b3c644 FIX: Splitcolumns must return an empty array if string was empty (#1315)
* Add test suite from rpraml

* FIX: Splitcolumns must return an empty array if string was empty

* Just the reference scripts
2018-03-02 12:16:29 +13:00
Roland PramlandRob Bygrave 0e1c815826 FIX: DataReader was read twice in exception-path (#1314) 2018-03-02 12:15:01 +13:00
Roland PramlandRob Bygrave 83b463040f ENH: Add sanity check to InExpression (#1313) 2018-03-02 12:14:09 +13:00
Roland PramlandRob Bygrave aea7d937eb ENH: added SqlRow factory method to RawSqlService (#1312)
* ENH: added SqlRow factory method to RawSqlService

* compile fixes

* ADD: sqlRow factory also to RawSqlBuilder
2018-03-02 12:13:16 +13:00
Roland PramlandRob Bygrave 78628ffad0 NEW: DB-with-Master option (#1311) 2018-03-02 12:12:34 +13:00
Roland PramlandRob Bygrave 56a742ad70 NEW: UUID version 1 generator (#1309)
* NEW: Uuid Type 1 generator

* Remove parameter (from other PR)

* FIX: Mac detection

* Have to use nodeId in generation
2018-03-02 12:09:21 +13:00
Roland Praml a4415b03ff FIX: limit the constraint names 2018-03-02 00:08:52 +01:00
Roland PramlandRob Bygrave e252f71792 ENH: Optimized binary form for UUID (Type 1) (#1308)
* ENH: Optimized binary form for UUID (Type 1)

* added missing parameter in test
2018-03-02 12:04:56 +13:00
Roland PramlandRob Bygrave 18ca3403ac NEW: flag to control, if javax.validation NotNull annotation should generate a NotNull column or not (#1306) 2018-03-02 12:02:02 +13:00
Roland PramlandRob Bygrave 94a118d635 FIX: do not set username and password for DbMigrationConfig (#1304) 2018-03-02 11:59:01 +13:00
Roland PramlandRob Bygrave efe325034b NEW: Ebean.checkUniqueness - you can check a bean, if save would work (#1303) 2018-03-02 11:57:44 +13:00
Roland PramlandRob Bygrave 0c368ed7b3 FIX: extra-ddl can contribute to "drop-all" script, otherwise the drop all script is not appyable to h2:file (#1301) 2018-03-02 11:48:35 +13:00
Roland PramlandRob Bygrave c1b022c123 NEW: FetchCountDistinct - query can return distinct values with their counts (#1300)
* NEW: FetchCountDistinct - query can return distinct values with their counts

* Have to disable asserts, because of #1298
2018-03-02 11:46:41 +13:00
Roland PramlandRob Bygrave 021d57f4b2 FIX: @Formula on @ManyToOne produces SQLException ... fix appends missing FROM clause for formula joins #1299 2018-03-02 11:43:08 +13:00
Roland PramlandRob Bygrave 11e180aa3f FIX: introduced TransactionalTestCase, so that changes to DB are not persisted (#1298)
* FIX: introduced TransactionalTestCase, so that changes to DB are not persisted

* removed license header
2018-03-02 09:10:21 +13:00
Rob Bygrave 9e2ddc9f8c [maven-release-plugin] prepare for next development iteration 2018-03-01 13:43:27 +13:00
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
608 changed files with 19686 additions and 3658 deletions
+1
View File
@@ -10,3 +10,4 @@ indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
spaces_around_operators = true
max_line_length = 130
+1
View File
@@ -20,3 +20,4 @@ profiling/
*.ipr
*.iws
.idea/
*uuid.state
+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
+298
View File
@@ -0,0 +1,298 @@
Overview of ebean.properties file
=================================
### DbMigration options
You can set individual values for different platforms when generating migrations
`dbmigration.platform.<PLATFORM>.databaseSequenceBatchSize`
For DB's using sequences this is the number of sequence values prefetched.
`dbmigration.platform.<PLATFORM>.dbuuid`
Control, how UUID generation should work - it affects DDL which column type is generated. Possible values:
- BINARY enforces binary UUID
- VARCHAR enforces varchar UUID
- BINARY_OPTIMIZED enforces binary-optimized UUID (makes sense only with Type1 ID)
- AUTO_BINARY use binary when platform does not support native UUID
- AUTO_BINARY_OPTIMIZED use binary-optimized when platform does not support native UUID
- AUTO_VARCHAR (default) use varchar when platform does not support UUID
`dbmigration.platform.<PLATFORM>.uuidStoreAsBinary`
Same as setting dbuuid to BINARY
`dbmigration.platform.<PLATFORM>.geometrySRID`
The Geometry SRID value (default 4326).
`dbmigration.platform.<PLATFORM>.idType`
The ID type (IDENTITY, SEQUENCE, GENERATOR, EXTERNAL)
`dbmigration.platform.<PLATFORM>.mapping`
Adjust the mapping. For example `BOOLEAN=integer(32);BIT=tinyint(3)`
`ebean.migration.applyPrefix`
Set this to "V" to be compatible with FlywayDB.
`ebean.migration.applySuffix`
Defaults to '.sql'
`ebean.migration.dbusername`
DB user used to run the DB migration.
`ebean.migration.dbpassword`
DB password used to run the DB migration.
`ebean.migration.generate`
Set to true if the DB migration should be generated on server start.
`ebean.migration.generatePendingDrop`
The version of a pending drop that should be generated as the next migration.
`ebean.migration.includeGeneratedFileComment`
TODO
`ebean.migration.metaTable`
For running migration the DB table that holds migration execution status. Default 'db_migration'
`ebean.migration.migrationPath`
Resource path for the migration xml and sql.
`ebean.migration.modelPath`
Subdirectory the model xml files go into. Default 'model'
`ebean.migration.modelSuffix`
Suffix. Default '.model.xml'
`ebean.migration.name`
Description text that can be appended to the version to become the ddl script file name
`ebean.migration.patchInsertOn`
migration versions that should be added to history without running.
`ebean.migration.patchResetChecksumOn`
migration versions that should have their checksum reset and not run.
Use this if you get a 'Checksum mismatch' error.
`ebean.migration.placeholders`
A comma and equals delimited placeholders that are substituted in SQL scripts when running migration (used by DB Migration runner only).
`ebean.migration.platform`
The database platform to generate migration DDL for.
`ebean.migration.run`
Flag set to true means to run any outstanding migrations on startup.
`ebean.migration.version`
The migration version name (typically FlywayDb compatible). Example: 1.1.1_2
### Ebean UUID options
`ebean.uuidVersion`
Controls, how the UUIDs are generated. Possible values
- VERSION4 (default) generate random V4 UUIDs,
- VERSION1 generate rfc4122 compliant Type 1 UUIDs (requires a state file)
- VERSION1RND generate fake Type 1 UUIDs
Note, that V1 UUIDs in conjunction with AUTO_BINARY_OPTIMIZED will give you the best index performance, but you MUST understand how this works to avoid collisions.
`ebean.uuidStateFile`
The state file that is Required to generate V1 UUIDs
### DocStoreConfig
`ebean.docstore.active`
True when the Document store integration is active/on.
`ebean.docstore.allowAllCertificates`
Set to true such that the client allows connections to invalid/self signed SSL certificates.
`ebean.docstore.bulkBatchSize`
The default batch size to use for the Bulk API calls.
`ebean.docstore.create`
When true the Document store should create any document indexes that don't already exist.
`ebean.docstore.dropCreate`
When true the Document store should drop and re-create document indexes.
`ebean.docstore.generateMapping`
Set to true means Ebean will generate mapping files on startup.
`ebean.docstore.mappingPath`
Resource path for the Document store mapping files.
`ebean.docstore.mappingSuffix`
Suffix used for mapping files.
`ebean.docstore.password`
Password credential that be used for authentication to document store.
`ebean.docstore.pathToResources`
Location of resources that mapping files are generated into.
`ebean.docstore.persist`
The default mode used by indexes. `DEFAULT|IGNORE|QUEUE|UPDATE`
`ebean.docstore.url`
The URL of the Document store server. For example: http://localhost:9200.
`ebean.docstore.username`
Credential that be used for authentication to document store.
### Ebean Autotune
`ebean.autoTune.mode`
The autoTune mode `DEFAULT_OFF|DEFAULT_ON|DEFAULT_ONIFEMPTY`
`ebean.autoTune.profiling`
TODO
`ebean.autoTune.profilingBase`
TODO
`ebean.autoTune.profilingFile`
TODO
`ebean.autoTune.profilingRate`
TODO
`ebean.autoTune.profilingUpdateFrequency`
TODO
`ebean.autoTune.queryTuning`
TODO
`ebean.autoTune.queryTuningAddVersion`
TODO
`ebean.autoTune.queryTuningFile`
TODO
`ebean.autoTune.skipGarbageCollectionOnShutdown`
TODO
`ebean.autoTune.skipProfileReportingOnShutdown`
TODO
### Ebean options
`ebean.allQuotedIdentifiers`
Quote all identifiers
`ebean.asOfSysPeriod`
Column used to support history and 'As of' queries. This column is a timestamp range or equivalent.
`ebean.asOfViewSuffix`
Suffix appended to the base table to derive the view that contains the union of the base table and the history table in order to support asOf queries.
`ebean.autoCommitMode`
Set to true if the DataSource uses autoCommit. Indicates that Ebean should use autoCommit friendly Transactions and TransactionManager.
`ebean.autoReadOnlyDataSource`
When true create a read only DataSource using readOnlyDataSourceConfig defaulting values from dataSourceConfig
`ebean.autostart`
Should the server start all
`ebean.backgroundExecutorSchedulePoolSize`
TODO
`ebean.backgroundExecutorShutdownSecs`
TODO
and some more todos:
`ebean.batch.mode`
`ebean.batch.size`
`ebean.changeLogAsync`
`ebean.changeLogIncludeInserts`
`ebean.classes`
`ebean.collectQueryOrigins`
`ebean.collectQueryStatsByNode`
`ebean.currentUserProvider`
`ebean.dataSourceJndiName`
`ebean.dataTimeZone`
`ebean.databaseBooleanFalse`
`ebean.databaseBooleanTrue`
`ebean.databasePlatform`
`ebean.databasePlatformName`
`ebean.databaseSequenceBatchSize`
ebean.datasource.h2.adminpassword
ebean.datasource.h2.adminusername
`ebean.dbEncrypt`
`ebean.dbOffline`
ebean.ddl.createOnly
ebean.ddl.generate
ebean.ddl.header
ebean.ddl.initSql
ebean.ddl.run
ebean.ddl.seedSql
ebean.defaultDeleteMissingChildren
ebean.defaultOrderById
ebean.disableClasspathSearch
ebean.disableL2Cache
ebean.docStoreOnly
ebean.docstore.active
ebean.docstore.allowAllCertificates
ebean.docstore.bulkBatchSize
ebean.docstore.create
ebean.docstore.dropCreate
ebean.docstore.generateMapping
ebean.docstore.mappingPath
ebean.docstore.mappingSuffix
ebean.docstore.password
ebean.docstore.pathToResources
ebean.docstore.persist
ebean.docstore.url
ebean.docstore.username
ebean.encryptDeployManager
ebean.encryptKeyManager
ebean.encryptor
ebean.explicitTransactionBeginMode
ebean.expressionEqualsWithNullAsNoop
ebean.expressionNativeIlike
ebean.geometrySRID
ebean.historyTableSuffix
ebean.jdbcFetchSizeFindEach
ebean.jdbcFetchSizeFindList
ebean.jodaLocalTimeMode
ebean.jsonDateTime
ebean.jsonInclude
ebean.lazyLoadBatchSize
ebean.localTimeWithNanos
ebean.namingConvention.schema
ebean.namingConvention.sequenceFormat
ebean.namingConvention.useForeignKeyPrefix
ebean.namingconvention
ebean.notifyL2CacheInForeground
ebean.packages
ebean.persistBatch
ebean.persistBatchOnCascade
ebean.persistBatchSize
ebean.persistBatching
ebean.persistenceContextScope
ebean.profiling
ebean.profiling.directory
ebean.profiling.includeProfileIds
ebean.profiling.minimumMicros
ebean.profiling.profilesPerFile
ebean.profiling.verbose
ebean.queryBatchSize
ebean.queryPlanTTLSeconds
ebean.search.packages
ebean.serverCachePlugin
ebean.skipCacheAfterWrite
ebean.slowQueryMillis
ebean.tenant.catalogProvider
ebean.tenant.currentTenantProvider
ebean.tenant.mode
ebean.tenant.partitionColumn
ebean.tenant.schemaProvider
ebean.updateAllPropertiesInBatch
ebean.updateChangesOnly
ebean.updatesDeleteMissingChildren
ebean.useJavaxValidationNotNull
ebean.useJtaTransactionManager
+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
+26 -12
View File
@@ -9,7 +9,7 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.10.2</version>
<version>11.14.2</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.10.2</tag>
<tag>ebean-11.14.2</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>
@@ -127,7 +136,7 @@
<dependency>
<groupId>org.antlr</groupId>
<artifactId>antlr4-runtime</artifactId>
<version>4.6</version>
<version>4.7.1</version>
</dependency>
<!-- Jackson core used internally by Ebean -->
@@ -191,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>
@@ -215,7 +224,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-agent</artifactId>
<version>11.9.1</version>
<version>11.10.1</version>
<scope>test</scope>
</dependency>
@@ -288,7 +297,7 @@
<plugin>
<groupId>io.ebean</groupId>
<artifactId>ebean-maven-plugin</artifactId>
<version>11.9.1</version>
<version>11.10.1</version>
<executions>
<execution>
<id>test</id>
@@ -321,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);
}
}
@@ -0,0 +1,27 @@
package io.ebean;
/**
* Enumeration to use with {@link Query#setCountDistinct(CountDistinctOrder)}.
* @author Roland Praml, FOCONIS AG
*
*/
public enum CountDistinctOrder {
NO_ORDERING,
/** order by attribute ascending */
ATTR_ASC,
/** order by attribute descending */
ATTR_DESC,
/** order by count ascending and attribute ascending */
COUNT_ASC_ATTR_ASC,
/** order by count ascending and attribute descending */
COUNT_ASC_ATTR_DESC,
/** order by count descending and attribute ascending */
COUNT_DESC_ATTR_ASC,
/** order by count descending and attribute descending */
COUNT_DESC_ATTR_DESC,
}
+33
View File
@@ -0,0 +1,33 @@
package io.ebean;
import java.io.Serializable;
/**
* Holds a distinct value with it's count.
* (Used with {@link Query#findSingleAttributeList()} and {@link Query#setCountDistinct(CountDistinctOrder)}.)
* @author Roland Praml, FOCONIS AG
*/
public class CountedValue<A> implements Serializable {
private static final long serialVersionUID = -2267971668356749695L;
private final A value;
private final long count;
public CountedValue(A value, long count) {
this.value = value;
this.count = count;
}
public long getCount() {
return count;
}
public A getValue() {
return value;
}
@Override
public String toString() {
return count + ": " + value;
}
}
+133
View File
@@ -0,0 +1,133 @@
package io.ebean;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* Query for performing native SQL queries that return DTO Bean's.
* <p>
* These beans are just normal classes. They must have public constructors
* and setters.
* <p>
* Constructors with arguments are used if the number of constructor arguments
* matches the number of columns in the resultSet.
* </p>
* <p>
* If the number of columns in the resultSet is greater than the largest constructor
* then the largest constructor is used for the first columns and remaining columns
* are mapped by setter methods.
* </p>
*
* <pre>{@code
*
* // CustomerDto is just a 'bean like' class
* // with public constructor(s) and public setter methods
*
* String sql = "select id, name from customer where name like :name and status_code = :status";
*
* List<CustomerDto> beans =
* Ebean.findDto(CustomrDto.class, sql)
* .setParameter("name", "Acme%")
* .setParameter("status", "ACTIVE")
* .findList();
*
* }</pre>
*/
public interface DtoQuery<T> {
/**
* Execute the query returning a list.
*/
@Nonnull
List<T> findList();
/**
* Execute the query iterating a row at a time.
* <p>
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
* </p>
*/
void findEach(Consumer<T> consumer);
/**
* Execute the query iterating a row at a time with the ability to stop consuming part way through.
* <p>
* Returning false after processing a row stops the iteration through the query results.
* </p>
* <p>
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
* </p>
*/
void findEachWhile(Predicate<T> consumer);
/**
* Execute the query returning a single bean.
*/
@Nullable
T findOne();
/**
* Execute the query returning an optional bean.
*/
@Nonnull
Optional<T> findOneOrEmpty();
/**
* The same as bind for named parameters.
*/
DtoQuery<T> setParameter(String name, Object value);
/**
* The same as bind for positioned parameters.
*/
DtoQuery<T> setParameter(int position, Object value);
/**
* Set the index of the first row of the results to return.
*/
DtoQuery<T> setFirstRow(int firstRow);
/**
* Set the maximum number of query results to return.
*/
DtoQuery<T> setMaxRows(int maxRows);
/**
* When resultSet columns are not able to be mapped to a bean property then instead of
* throwing effectively skip reading that column.
*/
DtoQuery<T> setRelaxedMode();
/**
* Set a label on the query to make it easier to identify queries related to query execution statistics.
*
* @param label A label that is unique to the DTO bean type.
*/
DtoQuery<T> setLabel(String label);
/**
* Set a timeout on this query.
* <p>
* This will typically result in a call to setQueryTimeout() on a
* preparedStatement. If the timeout occurs an exception will be thrown - this
* will be a SQLException wrapped up in a PersistenceException.
* </p>
*
* @param secs the query timeout limit in seconds. Zero means there is no limit.
*/
DtoQuery<T> setTimeout(int secs);
/**
* A hint which for JDBC translates to the Statement.fetchSize().
* <p>
* Gives the JDBC driver a hint as to the number of rows that should be
* fetched from the database when more rows are needed for ResultSet.
* </p>
*/
DtoQuery<T> setBufferFetchSizeHint(int bufferFetchSizeHint);
}
+85 -12
View File
@@ -3,18 +3,21 @@ package io.ebean;
import io.ebean.annotation.TxIsolation;
import io.ebean.cache.ServerCacheManager;
import io.ebean.config.ServerConfig;
import io.ebean.plugin.Property;
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.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
@@ -114,6 +117,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 +138,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 +152,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);
@@ -376,7 +375,7 @@ public final class Ebean {
* Note that this provides an try finally alternative to using {@link #executeCall(TxScope, Callable)} or
* {@link #execute(TxScope, Runnable)}.
* </p>
*
* <p>
* <h3>REQUIRES_NEW example:</h3>
* <pre>{@code
* // Start a new transaction. If there is a current transaction
@@ -646,6 +645,65 @@ public final class Ebean {
return serverMgr.getDefaultServer().saveAll(beans);
}
/**
* This method checks the uniqueness of a bean. I.e. if the save will work. It will return the
* properties that violates an unique / primary key. This may be done in an UI save action to
* validate if the user has entered correct values.
* <p>
* Note: This method queries the DB for uniqueness of all indices, so do not use it in a batch update.
* <p>
* Note: This checks only the root bean!
* <p>
* <pre>{@code
*
* // there is a unique constraint on title
*
* Document doc = new Document();
* doc.setTitle("One flew over the cuckoo's nest");
* doc.setBody("clashes with doc1");
*
* Set<Property> properties = server().checkUniqueness(doc);
*
* if (properties.isEmpty()) {
* // it is unique ... carry on
*
* } else {
* // build a user friendly message
* // to return message back to user
*
* String uniqueProperties = properties.toString();
*
* StringBuilder msg = new StringBuilder();
*
* properties.forEach((it)-> {
* Object propertyValue = it.getVal(doc);
* String propertyName = it.getName();
* msg.append(" property["+propertyName+"] value["+propertyValue+"]");
* });
*
* // uniqueProperties > [title]
* // custom msg > property[title] value[One flew over the cuckoo's nest]
*
* }
*
* }</pre>
*
* @param bean The entity bean to check uniqueness on
* @return a set of Properties if constraint validation was detected or empty list.
*/
@Nonnull
public static Set<Property> checkUniqueness(Object bean) {
return serverMgr.getDefaultServer().checkUniqueness(bean);
}
/**
* Same as {@link #checkUniqueness(Object)}. but with given transaction.
*/
@Nonnull
public static Set<Property> checkUniqueness(Object bean, Transaction transaction) {
return serverMgr.getDefaultServer().checkUniqueness(bean, transaction);
}
/**
* Delete the bean.
* <p>
@@ -1058,6 +1116,21 @@ public final class Ebean {
return serverMgr.getDefaultServer().findNative(beanType, nativeSql);
}
/**
* Create a Query for DTO beans.
* <p>
* DTO beans are just normal bean like classes with public constructor(s) and setters.
* They do not need to be registered with Ebean before use.
* </p>
*
* @param dtoType The type of the DTO bean the rows will be mapped into.
* @param sql The SQL query to execute.
* @param <T> The type of the DTO bean.
*/
public static <T> DtoQuery<T> findDto(Class<T> dtoType, String sql) {
return serverMgr.getDefaultServer().findDto(dtoType, sql);
}
/**
* Create an Update query to perform a bulk update.
* <p>
+69 -1
View File
@@ -4,13 +4,13 @@ import io.ebean.annotation.TxIsolation;
import io.ebean.cache.ServerCacheManager;
import io.ebean.config.ServerConfig;
import io.ebean.meta.MetaInfoManager;
import io.ebean.plugin.Property;
import io.ebean.plugin.SpiServer;
import io.ebean.text.csv.CsvReader;
import io.ebean.text.json.JsonContext;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.NonUniqueResultException;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
@@ -424,6 +424,19 @@ public interface EbeanServer {
*/
<T> Update<T> createUpdate(Class<T> beanType, String ormUpdate);
/**
* Create a Query for DTO beans.
* <p>
* DTO beans are just normal bean like classes with public constructor(s) and setters.
* They do not need to be registered with Ebean before use.
* </p>
*
* @param dtoType The type of the DTO bean the rows will be mapped into.
* @param sql The SQL query to execute.
* @param <T> The type of the DTO bean.
*/
<T> DtoQuery<T> findDto(Class<T> dtoType, String sql);
/**
* Create a SqlQuery for executing native sql
* query statements.
@@ -1461,6 +1474,61 @@ public interface EbeanServer {
*/
int saveAll(Collection<?> beans, Transaction transaction) throws OptimisticLockException;
/**
* This method checks the uniqueness of a bean. I.e. if the save will work. It will return the
* properties that violates an unique / primary key. This may be done in an UI save action to
* validate if the user has entered correct values.
* <p>
* Note: This method queries the DB for uniqueness of all indices, so do not use it in a batch update.
* <p>
* Note: This checks only the root bean!
* <p>
* <pre>{@code
*
* // there is a unique constraint on title
*
* Document doc = new Document();
* doc.setTitle("One flew over the cuckoo's nest");
* doc.setBody("clashes with doc1");
*
* Set<Property> properties = server().checkUniqueness(doc);
*
* if (properties.isEmpty()) {
* // it is unique ... carry on
*
* } else {
* // build a user friendly message
* // to return message back to user
*
* String uniqueProperties = properties.toString();
*
* StringBuilder msg = new StringBuilder();
*
* properties.forEach((it)-> {
* Object propertyValue = it.getVal(doc);
* String propertyName = it.getName();
* msg.append(" property["+propertyName+"] value["+propertyValue+"]");
* });
*
* // uniqueProperties > [title]
* // custom msg > property[title] value[One flew over the cuckoo's nest]
*
* }
*
* }</pre>
*
* @param bean The entity bean to check uniqueness on
* @return a set of Properties if constraint validation was detected or empty list.
*/
@Nonnull
Set<Property> checkUniqueness(Object bean);
/**
* Same as {@link #checkUniqueness(Object)}. but with given transaction.
*/
@Nonnull
Set<Property> checkUniqueness(Object bean, Transaction transaction);
/**
* Marks the entity bean as dirty.
* <p>
@@ -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;
}
}
@@ -480,6 +480,36 @@ public interface ExpressionList<T> {
*/
Query<T> setUseQueryCache(CacheMode useCache);
/**
* Extended version for setDistinct in conjunction with "findSingleAttributeList";
*
* <pre>{@code
*
* List<CountedValue<Order.Status>> orderStatusCount =
*
* Ebean.find(Order.class)
* .select("status")
* .where()
* .gt("orderDate", LocalDate.now().minusMonths(3))
*
* // fetch as single attribute with a COUNT
* .setCountDistinct(CountDistinctOrder.COUNT_DESC_ATTR_ASC)
* .findSingleAttributeList();
*
* for (CountedValue<Order.Status> entry : orderStatusCount) {
* System.out.println(" count:" + entry.getCount()+" orderStatus:" + entry.getValue() );
* }
*
* // produces
*
* count:3 orderStatus:NEW
* count:1 orderStatus:SHIPPED
* count:1 orderStatus:COMPLETE
*
* }</pre>
*/
Query<T> setCountDistinct(CountDistinctOrder orderBy);
/**
* Calls {@link #setUseQueryCache(CacheMode)} with <code>ON</code> or <code>OFF</code>.
*
+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 + "}";
}
+2 -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();
+60
View File
@@ -236,6 +236,14 @@ public interface Query<T> {
*/
Query<T> asDraft();
/**
* Convert the query to a DTO bean query.
* <p>
* We effectively use the underlying ORM query to build the SQL and then execute
* and map it into DTO beans.
*/
<D> DtoQuery<D> asDto(Class<D> dtoClass);
/**
* Cancel the query execution if supported by the underlying database and
* driver.
@@ -828,6 +836,11 @@ public interface Query<T> {
*/
<A> A findSingleAttribute();
/**
* Return true if this is countDistinct query.
*/
boolean isCountDistinct();
/**
* Execute the query returning either a single bean or null (if no matching
* bean is found).
@@ -1272,6 +1285,37 @@ public interface Query<T> {
*/
Query<T> setDistinct(boolean isDistinct);
/**
* Extended version for setDistinct in conjunction with "findSingleAttributeList";
*
* <pre>{@code
*
* List<CountedValue<Order.Status>> orderStatusCount =
*
* Ebean.find(Order.class)
* .select("status")
* .where()
* .gt("orderDate", LocalDate.now().minusMonths(3))
*
* // fetch as single attribute with a COUNT
* .setCountDistinct(CountDistinctOrder.COUNT_DESC_ATTR_ASC)
* .findSingleAttributeList();
*
* for (CountedValue<Order.Status> entry : orderStatusCount) {
* System.out.println(" count:" + entry.getCount()+" orderStatus:" + entry.getValue() );
* }
*
* // produces
*
* count:3 orderStatus:NEW
* count:1 orderStatus:SHIPPED
* count:1 orderStatus:COMPLETE
*
* }</pre>
*
*/
Query<T> setCountDistinct(CountDistinctOrder orderBy);
/**
* Return the first row value.
*/
@@ -1436,9 +1480,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 +1529,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 +1551,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
}
@@ -1,6 +1,7 @@
package io.ebean;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Builds RawSql instances from a SQL string and column mappings.
@@ -24,6 +25,13 @@ public interface RawSqlBuilder {
return XServiceProvider.rawSql().resultSet(resultSet, propertyNames);
}
/**
* Create and return a SqlRow based on the resultSet with dbTrueValue and binaryOptimizedUUID options.
*/
static SqlRow sqlRow(ResultSet resultSet, final String dbTrueValue, boolean binaryOptimizedUUID) throws SQLException {
return XServiceProvider.rawSql().sqlRow(resultSet, dbTrueValue, binaryOptimizedUUID);
}
/**
* Return an unparsed RawSqlBuilder. Unlike a parsed one this query can not be
* modified - so no additional WHERE or HAVING expressions can be added to
+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.
@@ -528,6 +529,16 @@ public final class EntityBeanIntercept implements Serializable {
}
}
/**
* Set old value but force it to be set regardless if it already has a value.
*/
private void setOriginalValueForce(int propertyIndex, Object value) {
if (origValues == null) {
origValues = new Object[owner._ebean_getPropertyNames().length];
}
origValues[propertyIndex] = value;
}
/**
* For forced update on a 'New' bean set all the loaded properties to changed.
*/
@@ -693,6 +704,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]) {
@@ -897,15 +911,19 @@ public final class EntityBeanIntercept implements Serializable {
if (setDirtyState) {
setOriginalValue(propertyIndex, origValue);
if (!dirty) {
dirty = true;
if (embeddedOwner != null) {
// Cascade dirty state from Embedded bean to parent bean
embeddedOwner._ebean_getIntercept().setEmbeddedDirty(embeddedOwnerIndex);
}
if (nodeUsageCollector != null) {
nodeUsageCollector.setModified();
}
setDirtyStatus();
}
}
private void setDirtyStatus() {
if (!dirty) {
dirty = true;
if (embeddedOwner != null) {
// Cascade dirty state from Embedded bean to parent bean
embeddedOwner._ebean_getIntercept().setEmbeddedDirty(embeddedOwnerIndex);
}
if (nodeUsageCollector != null) {
nodeUsageCollector.setModified();
}
}
}
@@ -1045,9 +1063,25 @@ public final class EntityBeanIntercept implements Serializable {
}
/**
* Explicitly set an old value.
* Explicitly set an old value with force (the old value is forced even it is already set).
*/
public void setOldValue(int propertyIndex, Object oldValue) {
setChangedPropertyValue(propertyIndex, true, oldValue);
setChangedProperty(propertyIndex);
setOriginalValueForce(propertyIndex, oldValue);
setDirtyStatus();
}
/**
* 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>
@@ -447,13 +465,20 @@ public class DbMigrationConfig {
metaTable = properties.get("migration.metaTable", metaTable);
runPlaceholders = properties.get("migration.placeholders", runPlaceholders);
String adminUser = properties.get("datasource." + serverName + ".username", dbUsername);
adminUser = properties.get("datasource." + serverName + ".adminusername", adminUser);
//Do not set user and pass from "datasource.db.username"
//There is a null test in MigrationRunner::getConnection to handle this
//String adminUser = properties.get("datasource." + serverName + ".username", dbUsername);
String adminUser = properties.get("datasource." + serverName + ".adminusername", dbUsername);
dbUsername = properties.get("migration.dbusername", adminUser);
String adminPwd = properties.get("datasource." + serverName + ".password", dbPassword);
adminPwd = properties.get("datasource." + serverName + ".adminpassword", adminPwd);
//String adminPwd = properties.get("datasource." + serverName + ".password", dbPassword);
String adminPwd = properties.get("datasource." + serverName + ".adminpassword", dbPassword);
dbPassword = properties.get("migration.dbpassword", adminPwd);
ddlHeader = properties.get("ddl.header", ddlHeader);
if (ddlHeader != null && !ddlHeader.isEmpty()) {
ddlHeader = StringHelper.replaceString(ddlHeader, "${version}", EbeanVersion.getVersion());
ddlHeader = StringHelper.replaceString(ddlHeader, "${timestamp}", ZonedDateTime.now().format( DateTimeFormatter.ISO_INSTANT ));
}
}
/**
@@ -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);
}
}
+212 -14
View File
@@ -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.
*/
@@ -286,7 +293,7 @@ public class ServerConfig {
/**
* When true create a read only DataSource using readOnlyDataSourceConfig defaulting values from dataSourceConfig.
* I believe this will default to true in some future release (as it has a nice performance benefit).
*
* <p>
* autoReadOnlyDataSource is an unfortunate name for this config option but I haven't come up with a better one.
*/
private boolean autoReadOnlyDataSource;
@@ -365,6 +372,16 @@ public class ServerConfig {
*/
private DbTypeConfig dbTypeConfig = new DbTypeConfig();
/**
* The UUID version to use.
*/
private UuidVersion uuidVersion = UuidVersion.VERSION4;
/**
* The UUID state file (for Version 1 UUIDs).
*/
private String uuidStateFile = "ebean-uuid.state";
private List<IdGenerator> idGenerators = new ArrayList<>();
private List<BeanFindController> findControllers = new ArrayList<>();
private List<BeanPersistController> persistControllers = new ArrayList<>();
@@ -463,6 +480,19 @@ public class ServerConfig {
*/
private boolean disableL2Cache;
/**
* Should the javax.validation.constraints.NotNull enforce a notNull column in DB.
* If set to false, use io.ebean.annotation.NotNull or Column(nullable=true).
*/
private boolean useJavaxValidationNotNull = true;
/**
* 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 +503,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 +545,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 +1765,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.
*/
@@ -1837,6 +1900,34 @@ public class ServerConfig {
this.dbTypeConfig.setDbUuid(dbUuid);
}
/**
* Returns the UUID version mode.
*/
public UuidVersion getUuidVersion() {
return uuidVersion;
}
/**
* Sets the UUID version mode.
*/
public void setUuidVersion(UuidVersion uuidVersion) {
this.uuidVersion = uuidVersion;
}
/**
* Return the UUID state file.
*/
public String getUuidStateFile() {
return uuidStateFile;
}
/**
* Set the UUID state file.
*/
public void setUuidStateFile(String uuidStateFile) {
this.uuidStateFile = uuidStateFile;
}
/**
* Return true if LocalTime should be persisted with nanos precision.
*/
@@ -2551,7 +2642,8 @@ public class ServerConfig {
* Load settings from ebean.properties.
*/
public void loadFromProperties() {
loadFromProperties(PropertyMap.defaultProperties());
this.properties = PropertiesLoader.load();
configureFromProperties();
}
/**
@@ -2559,24 +2651,41 @@ public class ServerConfig {
*/
public void loadFromProperties(Properties properties) {
// keep the properties used for configuration so that these are available for plugins
this.properties = properties;
this.properties = PropertiesLoader.eval(properties);
configureFromProperties();
}
/**
* Load the settings from the given properties
*/
private void configureFromProperties() {
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
}
/**
@@ -2623,6 +2732,7 @@ public class ServerConfig {
*/
protected void loadDataSourceSettings(PropertiesWrapper p) {
dataSourceConfig.loadSettings(p.properties, name);
readOnlyDataSourceConfig.loadSettings(p.properties, name);
}
/**
@@ -2680,9 +2790,11 @@ 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);
useJavaxValidationNotNull = p.getBoolean("useJavaxValidationNotNull", useJavaxValidationNotNull);
autoReadOnlyDataSource = p.getBoolean("autoReadOnlyDataSource", autoReadOnlyDataSource);
backgroundExecutorSchedulePoolSize = p.getInt("backgroundExecutorSchedulePoolSize", backgroundExecutorSchedulePoolSize);
@@ -2716,6 +2828,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 +2853,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) {
@@ -2748,6 +2862,10 @@ public class ServerConfig {
if (p.getBoolean("uuidStoreAsBinary", false)) {
dbTypeConfig.setDbUuid(DbUuid.BINARY);
}
uuidVersion = p.getEnum(UuidVersion.class, "uuidVersion", uuidVersion);
uuidStateFile = p.get("uuidStateFile", uuidStateFile);
localTimeWithNanos = p.getBoolean("localTimeWithNanos", localTimeWithNanos);
jodaLocalTimeMode = p.get("jodaLocalTimeMode", jodaLocalTimeMode);
@@ -2768,6 +2886,22 @@ public class ServerConfig {
ddlInitSql = p.get("ddl.initSql", ddlInitSql);
ddlSeedSql = p.get("ddl.seedSql", ddlSeedSql);
// read tenant-configuration from config:
// tenant.mode = NONE | DB | SCHEMA | CATALOG | PARTITION
String mode = p.get("tenant.mode");
if (mode != null) {
for (TenantMode value : TenantMode.values()) {
if (value.name().equalsIgnoreCase(mode)) {
tenantMode = value;
break;
}
}
}
currentTenantProvider = createInstance(p, CurrentTenantProvider.class, "tenant.currentTenantProvider", currentTenantProvider);
tenantCatalogProvider = createInstance(p, TenantCatalogProvider.class, "tenant.catalogProvider", tenantCatalogProvider);
tenantSchemaProvider = createInstance(p, TenantSchemaProvider.class, "tenant.schemaProvider", tenantSchemaProvider);
tenantPartitionColumn = p.get("tenant.partitionColumn", tenantPartitionColumn);
classes = getClasses(p);
}
@@ -2900,6 +3034,44 @@ public class ServerConfig {
this.disableL2Cache = disableL2Cache;
}
/**
* Returns if we use javax.validation.constraints.NotNull
*/
public boolean isUseJavaxValidationNotNull() {
return useJavaxValidationNotNull;
}
/**
* Controls if Ebean should ignore <code>&x64;javax.validation.contstraints.NotNull</code>
* with respect to generating a <code>NOT NULL</code> column.
* <p>
* Normally when Ebean sees javax NotNull annotation it means that column is defined as NOT NULL.
* Set this to <code>false</code> and the javax NotNull annotation is effectively ignored (and
* we instead use Ebean's own NotNull annotation or JPA Column(nullable=false) annotation.
*/
public void setUseJavaxValidationNotNull(boolean useJavaxValidationNotNull) {
this.useJavaxValidationNotNull = useJavaxValidationNotNull;
}
/**
* 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.
*/
@@ -2930,32 +3102,45 @@ public class ServerConfig {
*/
public enum DbUuid {
/**
* Store using native UUID in H2 and Postgres and otherwise fallback to VARCHAR(40).
*/
AUTO_VARCHAR(true, false),
AUTO_VARCHAR(true, false, false),
/**
* Store using native UUID in H2 and Postgres and otherwise fallback to BINARY(16).
*/
AUTO_BINARY(true, true),
AUTO_BINARY(true, true, false),
/**
* Store using native UUID in H2 and Postgres and otherwise fallback to BINARY(16) with optimized packing.
*/
AUTO_BINARY_OPTIMIZED(true, true, true),
/**
* Store using DB VARCHAR(40).
*/
VARCHAR(false, false),
VARCHAR(false, false, false),
/**
* Store using DB BINARY(16).
*/
BINARY(false, true);
BINARY(false, true, false),
/**
* Store using DB BINARY(16).
*/
BINARY_OPTIMIZED(false, true, true);
boolean nativeType;
boolean binary;
boolean binaryOptimized;
DbUuid(boolean nativeType, boolean binary) {
DbUuid(boolean nativeType, boolean binary, boolean binaryOptimized) {
this.nativeType = nativeType;
this.binary = binary;
this.binaryOptimized = binaryOptimized;
}
/**
@@ -2971,5 +3156,18 @@ public class ServerConfig {
public boolean useBinary() {
return binary;
}
/**
* Return true, if optimized packing should be used.
*/
public boolean useBinaryOptimized() {
return binaryOptimized;
}
}
public enum UuidVersion {
VERSION4,
VERSION1,
VERSION1RND
}
}
+22 -6
View File
@@ -8,32 +8,40 @@ public enum TenantMode {
/**
* No multi-tenancy.
*/
NONE(false),
NONE(false, true),
/**
* Each Tenant has their own Database (javax.sql.DataSource)
*/
DB(true),
DB(true, false),
/**
* Each Tenant has their own Database schema.
*/
SCHEMA(true),
SCHEMA(true, false),
/**
* Each Tenant has their own Database but with in connection pool
*/
CATALOG(true),
CATALOG(true, false),
/**
* Tenants share tables but have a discriminator/partition column that partitions the data.
*/
PARTITION(false);
PARTITION(false, true),
/**
* Each Tenant has their own Database (javax.sql.DataSource), and there is also one master-database
* (that holds configuration e.g.)
*/
DB_WITH_MASTER(true, true);
boolean dynamicDataSource;
boolean ddlEnabled;
TenantMode(boolean dynamicDataSource) {
TenantMode(boolean dynamicDataSource, boolean ddlEnabled) {
this.dynamicDataSource = dynamicDataSource;
this.ddlEnabled = ddlEnabled;
}
/**
@@ -42,4 +50,12 @@ public enum TenantMode {
public boolean isDynamicDataSource() {
return dynamicDataSource;
}
/**
* Returns true, if DDL is enabled.
*/
public boolean isDdlEnabled() {
return ddlEnabled;
}
}
@@ -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);
}
}
@@ -35,10 +35,12 @@ public class OraclePlatform extends DatabasePlatform {
dbIdentity.setSupportsIdentity(true);
dbIdentity.setSupportsGetGeneratedKeys(true);
this.treatEmptyStringsAsNull = true;
this.dbDefaultValue.setFalse("0");
this.dbDefaultValue.setTrue("1");
this.dbDefaultValue.setNow("current_timestamp");
this.likeClause = "like ? escape '|'";
this.specialLikeCharacters = new char[] { '%', '_', '|' };
this.treatEmptyStringsAsNull = true;
this.likeClauseRaw = "like ?";
this.exceptionTranslator =
new SqlErrorCodes()
@@ -51,7 +53,7 @@ public class OraclePlatform extends DatabasePlatform {
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));
@@ -72,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
@@ -89,8 +90,4 @@ public class OraclePlatform extends DatabasePlatform {
}
}
@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,170 @@
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() {
String skipYaml = System.getProperty("ebeanSkipYaml");
if (!"true".equals(skipYaml)) {
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) {
try {
yamlLoader.load(resource(resourcePath, source));
} catch (Exception e) {
log.warn("Failed to read yml from:" + resourcePath, e);
}
}
}
void loadProperties(String resourcePath, Source source) {
InputStream is = resource(resourcePath, source);
if (is != null) {
loadProperties(is);
}
}
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,85 @@
package io.ebean.config.properties;
import java.util.Enumeration;
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);
}
/**
* Return a copy of the properties with 'eval' run on all the values.
* This resolves expressions like ${HOME} etc.
*/
public static Properties eval(Properties properties) {
Properties evalCopy = new Properties();
Enumeration<?> names = properties.propertyNames();
while (names.hasMoreElements()) {
String name = (String)names.nextElement();
String value = PropertyEval.eval(properties.getProperty(name));
evalCopy.setProperty(name, value);
}
return evalCopy;
}
}
@@ -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,58 @@
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;
}
@SuppressWarnings("unchecked")
void load(InputStream is) {
if (is != null) {
for (Object map : yaml.loadAll(is)) {
loadMap((Map<String, Object>)map, 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.
*/
@@ -12,6 +12,11 @@ public interface MetaInfoManager {
*/
List<MetaTimedMetric> collectTransactionStatistics(boolean reset);
/**
* Collect query plan statistics (new, will migrate ORM query stats over to this).
*/
List<MetaQueryMetric> collectQueryStatistics(boolean reset);
/**
* Collect and return the non-empty query plan statistics for all the beans.
* <p>
@@ -0,0 +1,23 @@
package io.ebean.meta;
/**
* Query execution metrics.
*/
public interface MetaQueryMetric extends MetaTimedMetric {
/**
* The type of entity or DTO bean.
*/
Class<?> getType();
/**
* The label for the query (can be null).
*/
String getLabel();
/**
* The actual SQL of the query.
*/
String getSql();
}
@@ -27,17 +27,24 @@ public interface MetaTimedMetric {
long getCount();
/**
* Return the total execution time.
* Return the total execution time in micros.
*/
long getTotal();
/**
* Return the max execution time.
* Return the max execution time in micros.
*/
long getMax();
/**
* Return the mean execution time.
* Return the mean execution time in micros.
*/
long getMean();
/**
* Return the total beans or rows processed or loaded.
*
* This will be 0 if the metric isn't a query plan (like transaction execution statistics).
*/
long getBeanCount();
}
@@ -2,8 +2,10 @@ package io.ebean.service;
import io.ebean.RawSql;
import io.ebean.RawSqlBuilder;
import io.ebean.SqlRow;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Service provided by Ebean for parsing and column mapping raw SQL queries.
@@ -24,4 +26,13 @@ public interface SpiRawSqlService {
* Unparsed SQL so explicit column mapping expected.
*/
RawSqlBuilder unparsed(String sql);
/**
* Create based on a JDBC ResultSet.
*
* @param resultSet The ResultSet row to read as a SqlRow
* @param dbTrueValue The DB true value
* @param binaryOptimizedUUID Flag set to true if the UUID value is stored as optimised binary(16)
*/
SqlRow sqlRow(ResultSet resultSet, String dbTrueValue, boolean binaryOptimizedUUID) throws SQLException;
}
@@ -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;
@@ -102,7 +104,7 @@ public class AnnotationUtil {
}
/**
* Finds all annotations recusively for a class and its superclasses.
* Finds all annotations recusively for a class and its superclasses or interfaces.
*/
public static <A extends Annotation> Set<A> findAnnotationsRecursive(Class<?> clazz, Class<A> annotationType) {
if (annotationType == null) {
@@ -110,13 +112,28 @@ public class AnnotationUtil {
}
Set<A> ret = new LinkedHashSet<>();
Set<Annotation> visited = new HashSet<>();
while (clazz != null && clazz != Object.class) {
findMetaAnnotations(clazz, annotationType, ret, visited);
Set<Class<?>> visitedInterfaces = new HashSet<>();
while (clazz != null && !clazz.getName().startsWith("java.lang.")) {
findMetaAnnotationsRecursive(clazz, annotationType, ret, visited, visitedInterfaces);
clazz = clazz.getSuperclass();
}
return ret;
}
/**
* Searches the interfaces for annotations.
*/
private static <A extends Annotation> void findMetaAnnotationsRecursive(Class<?> clazz,
Class<A> annotationType, Set<A> ret,
Set<Annotation> visited, Set<Class<?>> visitedInterfaces) {
findMetaAnnotations(clazz, annotationType, ret, visited);
for (Class<?> iface : clazz.getInterfaces()) {
if (!iface.getName().startsWith("java.lang.") && visitedInterfaces.add(iface)) {
findMetaAnnotationsRecursive(iface, annotationType, ret, visited, visitedInterfaces);
}
}
}
/**
* Perform the search algorithm avoiding endless recursion by tracking which
* annotations have already been visited.
@@ -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 + "]";
}
@@ -0,0 +1,52 @@
package io.ebeaninternal.api;
import io.ebean.DtoQuery;
import io.ebeaninternal.server.dto.DtoMappingRequest;
import io.ebeaninternal.server.dto.DtoQueryPlan;
/**
* Internal extension to DtoQuery.
*/
public interface SpiDtoQuery<T> extends DtoQuery<T>, SpiSqlBinding {
/**
* Return the key for query plan.
*/
String planKey();
/**
* Get the query plan for the cache.
*/
DtoQueryPlan getQueryPlan(Object planKey);
/**
* Build the query plan.
*/
DtoQueryPlan buildPlan(DtoMappingRequest request);
/**
* Put the query plan into the cache.
*/
void putQueryPlan(Object planKey, DtoQueryPlan plan);
/**
* Return true if the query is in relaxed mapping mode.
*/
boolean isRelaxedMode();
/**
* Return the label for the query.
*/
String getLabel();
/**
* Return the associated DTO bean type.
*/
Class<T> getType();
/**
* Return an underlying ORM query (if this query is built from an ORM query).
*/
SpiQuery<?> getOrmQuery();
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.api;
import io.ebean.DtoQuery;
import io.ebean.EbeanServer;
import io.ebean.PersistenceContextScope;
import io.ebean.Query;
@@ -14,12 +15,15 @@ import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlHandler;
import io.ebeaninternal.server.core.SpiResultSet;
import io.ebeaninternal.server.core.timezone.DataTimeZone;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.query.CQuery;
import io.ebeaninternal.server.transaction.RemoteTransactionEvent;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* Service Provider extension to EbeanServer.
@@ -224,4 +228,33 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
*/
void scopedTransactionExit(Object returnOrThrowable, int opCode);
/**
* DTO findList query.
*/
<T> List<T> findDtoList(SpiDtoQuery<T> query);
/**
* DTO findOne query.
*/
<T> T findDtoOne(SpiDtoQuery<T> query);
/**
* DTO findEach query.
*/
<T> void findDtoEach(SpiDtoQuery<T> query, Consumer<T> consumer);
/**
* DTO findEachWhile query.
*/
<T> void findDtoEachWhile(SpiDtoQuery<T> query, Predicate<T> consumer);
/**
* Return / wrap the ORM query as a DTO query.
*/
<D> DtoQuery<D> findDto(Class<D> dtoType, SpiQuery<?> ormQuery);
/**
* Execute the underlying ORM query returning as a JDBC ResultSet to map to DTO beans.
*/
SpiResultSet findResultSet(SpiQuery<?> ormQuery, SpiTransaction transaction);
}
@@ -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,7 @@
package io.ebeaninternal.api;
import io.ebean.CacheMode;
import io.ebean.EbeanServer;
import io.ebean.CountDistinctOrder;
import io.ebean.ExpressionList;
import io.ebean.OrderBy;
import io.ebean.PersistenceContextScope;
@@ -10,10 +10,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;
@@ -215,6 +215,11 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
*/
BeanDescriptor<T> getBeanDescriptor();
/**
* Return the query plan key.
*/
Object getQueryPlanKey();
/**
* Return the RawSql that was set to use for this query.
*/
@@ -338,7 +343,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 +572,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 +593,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.
*/
@@ -738,6 +753,16 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
*/
boolean isDistinct();
/**
* Return true if the Id property is manually included in the query (DTO queries).
*/
boolean isManualId();
/**
* Set to true when we only include the Id property if it is explicitly included in the select().
*/
void setManualId(boolean manualId);
/**
* Set default select clauses where none have been explicitly defined.
*/
@@ -813,4 +838,9 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
* Simplify nested expression lists where possible.
*/
void simplifyExpressions();
/**
* Returns the count distinct order setting.
*/
CountDistinctOrder getCountDistinctOrder();
}
@@ -0,0 +1,38 @@
package io.ebeaninternal.api;
/**
* SQL query binding (for SqlQuery and DtoQuery).
*/
public interface SpiSqlBinding {
/**
* Return the named or positioned parameters.
*/
BindParams getBindParams();
/**
* return the query.
*/
String getQuery();
/**
* Return the first row to fetch.
*/
int getFirstRow();
/**
* Return the maximum number of rows to fetch.
*/
int getMaxRows();
/**
* Return the query timeout.
*/
int getTimeout();
/**
* Return the hint for Statement.setFetchSize().
*/
int getBufferFetchSizeHint();
}
@@ -5,36 +5,6 @@ import io.ebean.SqlQuery;
/**
* SQL query - Internal extension to SqlQuery.
*/
public interface SpiSqlQuery extends SqlQuery {
/**
* Return the named or positioned parameters.
*/
BindParams getBindParams();
/**
* return the query.
*/
String getQuery();
/**
* Return the first row to fetch.
*/
int getFirstRow();
/**
* Return the maximum number of rows to fetch.
*/
int getMaxRows();
/**
* Return the query timeout.
*/
int getTimeout();
/**
* Return the hint for Statement.setFetchSize().
*/
int getBufferFetchSizeHint();
public interface SpiSqlQuery extends SqlQuery, SpiSqlBinding {
}
@@ -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);
}
@@ -22,7 +22,7 @@ public interface SpiTransactionManager {
/**
* Return the currently active transaction (can be null).
*/
SpiTransaction get();
SpiTransaction getActive();
/**
* Push an externally managed transaction into scope (e.g. Spring managed transaction).
@@ -47,7 +47,7 @@ public class DdlGenerator {
this.jaxbPresent = serverConfig.getClassLoadConfig().isJavaxJAXBPresent();
this.generateDdl = serverConfig.isDdlGenerate();
this.createOnly = serverConfig.isDdlCreateOnly();
if (serverConfig.getTenantMode().isDynamicDataSource() && serverConfig.isDdlRun()) {
if (!serverConfig.getTenantMode().isDdlEnabled() && serverConfig.isDdlRun()) {
log.warn("DDL can't be run on startup with TenantMode " + serverConfig.getTenantMode());
this.runDdl = false;
} else {
@@ -127,6 +127,14 @@ public class DdlGenerator {
protected void runDropSql() throws IOException {
if (!createOnly) {
String ignoreExtraDdl = System.getProperty("ebean.ignoreExtraDdl");
if (!"true".equalsIgnoreCase(ignoreExtraDdl) && jaxbPresent) {
String extraApply = ExtraDdlXmlReader.buildExtra(server.getDatabasePlatform().getName(), true);
if (extraApply != null) {
runScript(false, extraApply, "extra-dll");
}
}
if (dropAllContent == null) {
dropAllContent = readFile(getDropFileName());
}
@@ -142,7 +150,7 @@ public class DdlGenerator {
String ignoreExtraDdl = System.getProperty("ebean.ignoreExtraDdl");
if (!"true".equalsIgnoreCase(ignoreExtraDdl) && jaxbPresent) {
String extraApply = ExtraDdlXmlReader.buildExtra(server.getDatabasePlatform().getName());
String extraApply = ExtraDdlXmlReader.buildExtra(server.getDatabasePlatform().getName(), false);
if (extraApply != null) {
runScript(false, extraApply, "extra-dll");
}
@@ -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>
@@ -263,7 +268,7 @@ public class DefaultDbMigration implements DbMigration {
if (extraDdl != null) {
List<DdlScript> ddlScript = extraDdl.getDdlScript();
for (DdlScript script : ddlScript) {
if (ExtraDdlXmlReader.matchPlatform(dbPlatform.getName(), script.getPlatforms())) {
if (!script.isDrop() && ExtraDdlXmlReader.matchPlatform(dbPlatform.getName(), script.getPlatforms())) {
writeExtraDdl(migrationDir, script);
}
}
@@ -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,41 @@ 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);
DdlBuffer fkeyBuffer = write.applyForeignKeys();
alterTableAddForeignKey(fkeyBuffer, fkName, tableName, columns, refTable, refColumns);
if (indexName != null) {
String tableName = lowerTableName(request.table());
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();
}
alterTableAddForeignKey(fkeyBuffer, request);
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 +526,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 +543,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 +594,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 +624,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 +641,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 +690,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 +931,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 +966,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);
}
}
@@ -26,7 +26,7 @@ public class MySqlDdl extends PlatformDdl {
*/
@Override
public String dropIndex(String indexName, String tableName) {
return "drop index " + indexName + " on " + tableName;
return "drop index " + maxConstraintName(indexName) + " on " + tableName;
}
/**
@@ -34,7 +34,7 @@ public class MySqlDdl extends PlatformDdl {
*/
@Override
public String alterTableDropForeignKey(String tableName, String fkName) {
return "alter table " + tableName + " drop foreign key " + fkName;
return "alter table " + tableName + " drop foreign key " + maxConstraintName(fkName);
}
@Override
@@ -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,7 +15,6 @@ 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";
@@ -22,4 +22,30 @@ public class Oracle10Ddl extends PlatformDdl {
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,23 +1,25 @@
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;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.util.PlatformTypeConverter;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.util.VowelRemover;
import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
import io.ebeaninternal.dbmigration.migration.AlterColumn;
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 +64,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 +214,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 +228,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.
*/
@@ -243,7 +239,7 @@ public class PlatformDdl {
* Return the drop foreign key clause.
*/
public String alterTableDropForeignKey(String tableName, String fkName) {
return "alter table " + alterTableIfExists + tableName + " " + dropConstraintIfExists + " " + fkName;
return "alter table " + alterTableIfExists + tableName + " " + dropConstraintIfExists + " " + maxConstraintName(fkName);
}
/**
@@ -306,9 +302,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(";");
@@ -333,7 +327,7 @@ public class PlatformDdl {
* Return the drop index statement.
*/
public String dropIndex(String indexName, String tableName) {
return dropIndexIfExists + indexName;
return dropIndexIfExists + maxConstraintName(indexName);
}
/**
@@ -342,7 +336,7 @@ public class PlatformDdl {
public String createIndex(String indexName, String tableName, String[] columns) {
StringBuilder buffer = new StringBuilder();
buffer.append("create index ").append(indexName).append(" on ").append(tableName);
buffer.append("create index ").append(maxConstraintName(indexName)).append(" on ").append(tableName);
appendColumns(columns, buffer);
return buffer.toString();
@@ -351,49 +345,80 @@ 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(maxConstraintName(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).
*/
public String alterTableDropUniqueConstraint(String tableName, String uniqueConstraintName) {
return "alter table " + tableName + " " + dropUniqueConstraint + " " + uniqueConstraintName;
return "alter table " + tableName + " " + dropUniqueConstraint + " " + maxConstraintName(uniqueConstraintName);
}
/**
* Drop a unique constraint from the table.
*/
public String alterTableDropConstraint(String tableName, String constraintName) {
return "alter table " + tableName + " " + dropConstraint + " " + constraintName;
return "alter table " + tableName + " " + dropConstraint + " " + maxConstraintName(constraintName);
}
/**
@@ -401,10 +426,10 @@ 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 ");
buffer.append("alter table ").append(tableName).append(" add constraint ").append(maxConstraintName(uqName)).append(" unique ");
appendColumns(columns, buffer);
return buffer.toString();
}
@@ -418,17 +443,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();
@@ -485,14 +507,14 @@ public class PlatformDdl {
*/
public String alterTableAddCheckConstraint(String tableName, String checkConstraintName, String checkConstraint) {
return "alter table " + tableName + " " + addConstraint + " " + checkConstraintName + " " + checkConstraint;
return "alter table " + tableName + " " + addConstraint + " " + maxConstraintName(checkConstraintName) + " " + checkConstraint;
}
/**
* Alter column setting the default value.
*/
public String alterColumnDefaultValue(String tableName, String columnName, String defaultValue) {
String suffix = DdlHelp.isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + defaultValue;
String suffix = DdlHelp.isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + convertDefaultValue(defaultValue);
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + suffix;
}
@@ -586,4 +608,37 @@ 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 {
}
/**
* Shortens the given name to the maximum constraint name length of the platform in a deterministic way.
*
* First, all vowels are removed, If the string is still to long, 31 bits are taken from the hash code
* of the string and base36 encoded (10 digits and 26 chars) string.
*
* As 36^6 > 31^2, the resulting string is never longer as 6 chars.
*/
protected String maxConstraintName(String name) {
if (name.length() > platform.getMaxConstraintNameLength()) {
int hash = name.hashCode() & 0x7FFFFFFF;
name = VowelRemover.trim(name, 4);
if (name.length() > platform.getMaxConstraintNameLength()) {
return name.substring(0, platform.getMaxConstraintNameLength()-7) + "_" + Integer.toString(hash, 36);
}
}
return name;
}
}
@@ -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,15 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
public class SplitColumns {
/**
* Return as an array of string column names.
*/
public static String[] split(String columns) {
if (columns == null || columns.isEmpty()) {
return new String[0];
}
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();
@@ -36,7 +43,7 @@ public class SqlServerDdl extends PlatformDdl {
@Override
public String alterTableDropForeignKey(String tableName, String fkName) {
int pos = tableName.lastIndexOf('.');
String objectId = fkName;
String objectId = maxConstraintName(fkName);
if (pos != -1) {
objectId = tableName.substring(0, pos + 1) + fkName;
}
@@ -50,15 +57,16 @@ public class SqlServerDdl extends PlatformDdl {
@Override
public String dropIndex(String indexName, String tableName) {
return "IF EXISTS (SELECT name FROM sys.indexes WHERE object_id = OBJECT_ID('" + tableName +"','U') AND name = '" + indexName + "') drop index " + indexName + " ON " + tableName;
return "IF EXISTS (SELECT name FROM sys.indexes WHERE object_id = OBJECT_ID('" + tableName + "','U') AND name = '"
+ maxConstraintName(indexName) + "') drop index " + maxConstraintName(indexName) + " ON " + tableName;
}
/**
* 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 +83,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 ";
}
@@ -95,7 +103,7 @@ public class SqlServerDdl extends PlatformDdl {
@Override
public String alterTableDropUniqueConstraint(String tableName, String uniqueConstraintName) {
StringBuilder sb = new StringBuilder();
sb.append("IF (OBJECT_ID('").append(uniqueConstraintName).append("', 'UQ') IS NOT NULL) ");
sb.append("IF (OBJECT_ID('").append(maxConstraintName(uniqueConstraintName)).append("', 'UQ') IS NOT NULL) ");
sb.append(super.alterTableDropUniqueConstraint(tableName, uniqueConstraintName)).append(";\n");
sb.append(dropIndex(uniqueConstraintName, tableName));
return sb.toString();
@@ -114,9 +122,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(";");
@@ -140,7 +146,7 @@ public class SqlServerDdl extends PlatformDdl {
sb.append("if @Tmp is not null EXEC('alter table ").append(tableName).append(" drop constraint ' + @Tmp)$$");
} else {
sb.append("alter table ").append(tableName);
sb.append(" add default ").append(defaultValue).append(" for ").append(columnName);
sb.append(" add default ").append(convertDefaultValue(defaultValue)).append(" for ").append(columnName);
}
return sb.toString();
}
@@ -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;
}
}

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