Compare commits

...
Author SHA1 Message Date
Rob Bygrave eac50b8f5b [maven-release-plugin] prepare release ebean-11.2.2 2017-10-16 22:26:21 +13:00
Rob Bygrave 6e2712ea49 #1169 - Memory leak - DefaultTransactionThreadLocal holding reference to transaction 2017-10-16 22:14:48 +13:00
Rob Bygrave f6e5446dab #1169 - Memory leak - DefaultTransactionThreadLocal holding reference to transaction - test case 2017-10-16 22:13:41 +13:00
Koen De GrooteandRob Bygrave 4879d44da4 Bit of cleanup suggestions from intellij. (#1168) 2017-10-16 20:16:30 +13:00
rob bygrave 2c5bf98d0b Merge branch 'master' of github.com:ebean-orm/ebean 2017-10-09 21:13:32 +13:00
rob bygrave 45a571bd97 #1165 - ENH: Add to Finder - currentTransaction() and flush() methods ... (as useful shortcuts) 2017-10-09 21:13:12 +13:00
Roland PramlandRob Bygrave d0ca75c25b FIX: connection leak in query-iterator (#1164) 2017-10-09 20:46:02 +13:00
Roland PramlandRob Bygrave fb1c7667cc FIX: order by must be added in findEach also (#1163) 2017-10-09 20:28:36 +13:00
Roland PramlandRob Bygrave 1909ba1557 Fix like escaping for DB2, Oracle and MySql (#1162)
* Extended test case to show problem

* FIX: Proper escaping of backslash in like/contains queries

* Had to replace "like ?" assertions in tests with "like " as mysql will do a "like binary ? escape'|'" now to be case sensitive
2017-10-06 09:46:56 +13:00
Roland PramlandRob Bygrave e4eca4bbe9 FIX: Oto relations must match datatype (#1161) 2017-10-06 09:44:06 +13:00
Roland PramlandRob Bygrave b2438ecd71 FIX: do not generate unique-constraint for PK as it is useless (and oracle does not accept this) (#1160) 2017-10-06 09:43:30 +13:00
Roland PramlandRob Bygrave ed08567d06 FIX: bundle symbolic name in manifest (#1159) 2017-10-06 09:42:42 +13:00
Roland PramlandRob Bygrave bbe8abcc2e Can handle one to many & many to one relations in docstore only entities (#1158) 2017-10-06 09:42:05 +13:00
Roland PramlandRob Bygrave 1651d5e119 Misc platform fixes (#1157)
* Oracle does not return row count - so we probably cannot detect Optimistic locking

* DB2 wants a varchar null value (other platfroms are happy with this, too)

* FIX Test for DB2 platform
2017-10-05 21:51:09 +13:00
Roland PramlandRob Bygrave 64bf6d6a35 commit transaction only if active (#1156) 2017-10-05 11:04:12 +13:00
Roland PramlandRob Bygrave 47b0a917a2 No effective code change - fix imports and override annotations (#1155) 2017-10-05 10:55:48 +13:00
rob bygrave 2fe1a349c3 [maven-release-plugin] prepare for next development iteration 2017-10-05 01:36:46 +13:00
rob bygrave f168a8fd0d [maven-release-plugin] prepare release ebean-11.2.1 2017-10-05 01:36:30 +13:00
rob bygrave dda98c1dda Bump to 11.2.1-SNAPSHOT 2017-10-05 01:35:30 +13:00
rob bygrave 88a13c5416 #1154 - Remove deprecated TxCallable and TxRunnable - migrate to use Callable and Runnable 2017-10-05 01:24:53 +13:00
rob bygrave 16b8f5da35 Refactor EbeanServerFactory internals - use SpiContainerFactory interface and service loader 2017-10-05 01:12:07 +13:00
rob bygrave 3471f6d206 Refactor EbeanServerFactory internals - use SpiContainerShutdown interface for container shutdown mechanism 2017-10-05 00:57:40 +13:00
rob bygrave 0bf4c84bc8 #1153 - Refactor move SplitName into io.ebean.util 2017-10-05 00:41:32 +13:00
rob bygrave d366336ee5 #1153 - Refactor move ExtraTypeFactory out of io.ebean.plugin into io.ebeaninternal 2017-10-05 00:38:42 +13:00
rob bygrave bf3f8ecff5 Refactor plugin.BeanType addInheritanceWhere() to just take Query and not SpiQuery 2017-10-05 00:35:12 +13:00
rob bygrave 7524903aea #1152 - Refactor EJson internals extracting io.ebean.service.SpiJsonService 2017-10-05 00:29:43 +13:00
rob bygrave 90cf3fddc9 #1151 - Refactor add util AnnotationUtil ... moving common annotation searching methods there 2017-10-05 00:00:44 +13:00
rob bygrave 8e0f9f7591 Refactor - remove unused methods from StringHelper 2017-10-04 23:35:57 +13:00
rob bygrave 24936c0a41 Refactor - remove unused class ClassUtil 2017-10-04 23:34:13 +13:00
rob bygrave f02a552cdf #1150 - Refactor convert RawSql and RawSqlBuilder into interfaces and push the parsers into io.ebeaninternal 2017-10-04 23:03:12 +13:00
rob bygrave 5a5cb7ffbd #1149 - Refactor move PersistenceContextUtil into ... io.ebeaninternal 2017-10-04 21:12:39 +13:00
rob bygrave f85fa772bf #1147 - Refactor move DbMigration internals into io.ebeaninternal - change to use DbMigration.create() - move DbMigrationFactory.create() method 2017-10-04 21:06:55 +13:00
rob bygrave 9f9ec8ef9f #1147 - Refactor move DbMigration internals into io.ebeaninternal - change to use DbMigration.create() - move IdentityType handling into internal from DbIdentity 2017-10-04 20:49:04 +13:00
rob bygrave 62707caf64 #1147 - Refactor move DbMigration internals into io.ebeaninternal - change to use DbMigration.create() - move PlatformDdl into internal 2017-10-04 20:30:57 +13:00
rob bygrave c6fc7a032d #1147 - Refactor move DbMigration internals into io.ebeaninternal - change to use DbMigration.create() - DbOffline & ServerConfig 2017-10-04 19:44:29 +13:00
rob bygrave 10faa1fae6 #1147 - Refactor move DbMigration internals into io.ebeaninternal - change to use DbMigration.create() - initial move 2017-10-04 19:36:27 +13:00
Rob Bygrave 764f5ce4d4 [maven-release-plugin] prepare for next development iteration 2017-10-03 23:20:50 +13:00
Rob Bygrave 65ba231fc0 [maven-release-plugin] prepare release ebean-11.1.1 2017-10-03 23:20:35 +13:00
Rob Bygrave 1da2382e45 No effective change - javadoc only 2017-10-03 15:38:17 +13:00
Rob Bygrave a86af94a8c Reduce logging level only - no functional change 2017-10-03 15:35:21 +13:00
Rob Bygrave 8301cb634e #1027 - ENH: Add a Slow query event listener ... to log or listen to queries that exceed some defined execution time 2017-10-03 15:04:30 +13:00
Rob Bygrave 721654d033 #1140 - Error loading embedded instance
The issue was due to the loaded status not being recursively set on embedded
beans after inserts. The test case invoked lazy loading due to that which
lead to the issue.
2017-10-03 12:47:29 +13:00
Rob Bygrave fe1817ef02 No effective change - update test 2017-10-03 12:34:56 +13:00
Rob Bygrave fc996659f2 #1139 - NoSuchMethodError: javax.persistence.Embedded.prefix()Ljava/lang/String; 2017-10-03 11:02:38 +13:00
rob bygrave 368d22baa6 #1145 - Refactor @ChangeLog based on JSON documents for 'new values' and 'old values' 2017-10-03 01:31:37 +13:00
rob bygrave daf3d97266 #1144 - Refactor internals - move generated property setting into PersistRequestBean 2017-10-03 00:30:34 +13:00
rob bygrave f2179fca2d #1123 - 11.1.1 API change - moved PersistBatch, Platform, TxIsolation and TxType to io.ebean.annotation package 2017-09-15 21:48:53 +12:00
rob bygrave 2c436c3af5 #1123 - 11.1.1 API change - moved PersistBatch, Platform, TxIsolation and TxType to io.ebean.annotation package 2017-09-15 21:48:37 +12:00
Roland PramlandRob Bygrave 8d334ff2e8 Feature/contitional test runner (#1132)
* FIX: warnings (add generics, overrides, fix imports) - should be no effecive code change

* replaced deprecated methods by default methods

* FIX: more compiler warnings

* FIX more warnings - no code changes

* FIX: deprecated call to JsonParseException

* Remove unused code, check unused variables

* ADD: ContitonaltestRunner to skip tests for certain platforms

* Use annotations to control on which platforms a test shoud run

* FIX: compile errors
2017-09-15 21:03:03 +12:00
rob bygrave f3e4696498 [maven-release-plugin] prepare for next development iteration 2017-09-14 01:03:21 +12:00
rob bygrave 221260fa44 [maven-release-plugin] prepare release ebean-10.4.7 2017-09-14 01:03:07 +12:00
rob bygrave f8e7a79e9a #1131 - Migration broken in 10.4.6 due different classpath-scanner version 2017-09-14 00:56:37 +12:00
Roland PramlandRob Bygrave b66f18bfd6 Fix warnings 2 (#1130)
* FIX: warnings (add generics, overrides, fix imports) - should be no effecive code change

* replaced deprecated methods by default methods

* FIX: more compiler warnings

* FIX more warnings - no code changes

* FIX: deprecated call to JsonParseException

* Remove unused code, check unused variables
2017-09-14 00:32:48 +12:00
Roland PramlandRob Bygrave ac6708e0d0 Fix warnings (#1129)
* FIX: warnings (add generics, overrides, fix imports) - should be no effecive code change

* replaced deprecated methods by default methods

* FIX: more compiler warnings

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

* Collections form the cache have to be read only

* findCount not queried in query cache

* return modifiable collections from the cache

* FIX: Compile errors

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

* update reference models after adding test classes

* DbMigration:Improved History generation for sqlserver

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

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

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

* FIX: DbMigration is not executed on history table

* DbMigration: can drop comment now.

* No effective code change: completed ObjectFactory / fixed imports

* FIX: drop table comment

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

* NEW: Default values are written to DDL

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

* Reading DbMigration

* ADD: DbmIgrationGenerateTest

* Tables must be created first

* Sanity checks and some getters

* DdlMigration gets applied now

* ADD: Testcase and reference migration scripts

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

* CHG: searching namedQueries & indices also recursive in superclasses

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

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

* ADD: additional asserts to TestQueryCache

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

* FIX: H2: Add error code for locking

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

* FIX: connection string for several platforms

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

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

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

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

* Update ScalarTypeEnumStandard.java

* Update ScalarTypeEnumStandard.java

* Update ScalarTypeEnumStandard.java

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

* Forgot making one instance `private static final`.
2017-07-06 20:14:51 +12:00
rob bygrave 08263c4588 [maven-release-plugin] prepare for next development iteration 2017-07-05 21:41:12 +12:00
rob bygrave 1e88a52208 [maven-release-plugin] prepare release ebean-10.4.1 2017-07-05 21:40:58 +12:00
rob bygrave e770a42175 Bump to 10.4.1-SNAPSHOT (due to ServerConfig h2ProductionMode removal) 2017-07-05 21:39:12 +12:00
Thibault MeyerandRob Bygrave 35b467c04f JSON simple implementation for MSSQL (#1057)
- Use JSON_VALUE
  - Use NVARCHAR type

Signed-off-by: Thibault Meyer <meyer.thibault@gmail.com>
2017-07-03 20:45:48 +12:00
Roland PramlandRob Bygrave 2001539755 Sqlserver support (#1053)
* made some tests sqlserver ready (No effective code change in src/main)

* FIX enhance packages and let all testcases inherit from BaseTestCase, so that they can be launched directly from eclipse

* ADD sqldriver in pom and datasource

* FIX: DDL-generation for sqlserver - not yet all sqlserver tests passing
mvn verify -Ddatasource.default=mssql => Tests run: 1980, Failures: 22, Errors: 44, Skipped: 16

* BaseDdlHandlerTest checks against correct sqlserver-ddl now
2017-07-03 20:27:37 +12:00
rob bygrave e49c4bfe5a #867 - ENH: Add ability to globally use quoted identifiers on all tables and columns 2017-07-02 21:07:58 +12:00
rob bygrave 15be31fd5f #1051 - Remove ServerConfig h2ProductionMode ... means for testing with h2 explicitly set ddlGenerate and ddlRun 2017-07-02 11:42:56 +12:00
rob bygrave 4777ec46be #1050 - Bump db-migration to 10.1.10 2017-07-02 11:12:03 +12:00
rob bygrave 80ab731aec Use older postgres jdbc driver 2017-06-25 22:56:21 +12:00
rob bygrave 080bafe18f #1048 - Fix connection leak on failed findIterate() 2017-06-25 22:39:04 +12:00
rob bygrave e913f88f52 Fix test - TestCommitAndContinue ... close extra transactions used in asserts 2017-06-25 21:43:37 +12:00
rob bygrave e94c5484a0 #1047 - Fix connection leak on failed beginTransaction() 2017-06-25 21:42:51 +12:00
rob bygrave 4af00c51d3 travis - dist: trusty ... to get PG 9.6 2017-06-25 19:57:31 +12:00
rob bygrave 23d1256dbf Update to postgres 9.6 for travis test runs 2017-06-25 19:43:05 +12:00
rob bygrave ed0ef46d2e No effective change - update postgresql jdbc driver 2017-06-25 19:03:07 +12:00
rob bygrave 1860ded735 [maven-release-plugin] prepare for next development iteration 2017-06-25 16:01:50 +12:00
rob bygrave d432854b82 [maven-release-plugin] prepare release ebean-10.3.2 2017-06-25 16:01:37 +12:00
rob bygrave 93582c8ea3 Downgrade postgres jdbc driver for commit and continue test 2017-06-25 15:54:14 +12:00
rob bygrave 520d35b1d6 #963 - NullPointerException on no version provided (when setVersion(null) is used) 2017-06-25 14:44:13 +12:00
rob bygrave 8ca3599cbf #1046 Bump DB migration to 10.1.9 - fix for Checksum mismatch does not stop migration 2017-06-25 14:29:46 +12:00
rob bygrave 283ae74219 Update javadoc on ScalarTypeConverter. Should look to officially deprecate this in favor of AttributeConverter. 2017-06-24 11:35:58 +12:00
rob bygrave cebd3f92a4 #1045 - MySql - DbMigration generate wrong comment syntax - should be alter table comment = syntax 2017-06-24 11:23:21 +12:00
rob bygrave 4b2481d3ea #1039 Fix for - reset of dirty flag on L2 cached entities after lazy load 2017-06-23 23:20:17 +12:00
rob bygrave f1f2a416d3 No effective change - reformat only 2017-06-23 23:19:22 +12:00
rob bygrave 74dd97a55f No effective change 2017-06-23 23:18:57 +12:00
rob bygrave 3f04f7f363 Merge branch 'master' of https://github.com/SamhammerAG/ebean into SamhammerAG-master 2017-06-23 19:52:55 +12:00
rob bygrave 800f854a12 #1043 Additional test for RawSql with upper case SQL 2017-06-23 19:46:47 +12:00
rob bygrave bef9e6d54f Merge branch 'master' of github.com:ebean-orm/ebean 2017-06-23 19:43:45 +12:00
Tigran_DadaiantsandRob Bygrave 0e4c0ae6c3 #1043 RawSql should automatically map upper-case column alias to lower-case bean property (#1044) 2017-06-23 19:43:15 +12:00
rob bygrave a0ea9887df No effective change - remove unused imports 2017-06-23 19:37:24 +12:00
Roland PramlandRob Bygrave 0413c22e6e no effective code change - fixed warnings about serialization, override, raw-types (#1032) 2017-06-23 19:33:03 +12:00
rob bygrave 989ae44a6d Merge branch 'pr/id-in-as-collection' of https://github.com/FOCONIS/ebean into FOCONIS-pr/id-in-as-collection 2017-06-23 19:28:25 +12:00
rob bygrave 100d314e63 #1035 Update for - FIX: deleting and adding same bean in beanCollection leads to deletion in DB
If use Modify mode we need to listen for both adds and removes to support sorting (can't rely on undoDelete() due to ordering)
2017-06-19 23:34:44 +12:00
rob bygrave dbf4e99187 #1035 tests for - FIX: deleting and adding same bean in beanCollection leads to deletion in DB 2017-06-19 23:32:58 +12:00
Roland PramlandRob Bygrave 7b8a85106c FIX: deleting and adding same bean in beanCollection leads to deletion in DB (#1035) 2017-06-19 22:50:20 +12:00
rob bygrave 58c5f01e2b #1042 - JtaTransaction is TenantMode.DB aware 2017-06-19 22:28:15 +12:00
Roland PramlandRob Bygrave bc74d35651 FIX: correct behavior of "startsWith, "contains" etc (#1031)
* FIX: correct behavior of "startsWith" if string contains "like"-wildcards

* Removed likeClasues with disabled escaping in database platforms as bind values are properly escaped now.
2017-06-19 22:21:26 +12:00
Roland PramlandRob Bygrave a1f95bf25e Pr/id in as collection2 (#1034)
* ENH: Changed  idIn(List) to idIn(Collection)

* ADD: sanity checks in IdBinder & special case if ID-list is empty
2017-06-19 22:05:56 +12:00
rob bygrave c454e1f8a7 No effective change - add test 2017-06-19 21:53:35 +12:00
Koen De GrooteandRob Bygrave 5a3669e1db Noticed that this boolean parameter was ignored. (#1041)
After some quick analysis, implemented that way it probably should be used.

Of course, this could have been on purpose, please advise.
2017-06-19 21:39:58 +12:00
rob bygrave 7a2e0b3695 Bump to the later agent 2017-06-19 21:34:18 +12:00
AWoelfel ce6d6269cd Step to reproduce reset of dirty flag on L2 cached entities after lazy load 2017-06-08 15:07:32 +02:00
Roland Praml 7d800b7080 ENH: Changed idIn(List) to idIn(Collection) 2017-05-29 15:22:48 +02:00
rob bygrave 136154e247 [maven-release-plugin] prepare for next development iteration 2017-05-21 21:56:29 +12:00
rob bygrave 0a5ce1bee8 [maven-release-plugin] prepare release ebean-10.3.1 2017-05-21 21:56:16 +12:00
Koen De GrooteandRob Bygrave b4e2c89e2d Moved a few variables closer to the point where they are actually used, but only in cases where this means moving them past a (potential) return/throw statement. (#1029) 2017-05-21 21:51:58 +12:00
Koen De GrooteandRob Bygrave 9e2923f571 For debug and trace levels, either added isEnabled check or made arguments parameterized. (#1028) 2017-05-21 21:50:13 +12:00
rob bygrave d0c1ae76f4 #955 - Migration "add column" DDL with check constraint is missing constraint name 2017-05-21 01:23:55 +12:00
rob bygrave fe9500e6d0 #1011 - Create valid migration scripts for non-Postgres databases and @DbArray columns.
Merge branch 'test/dbarray-create-migration' of https://github.com/FOCONIS/ebean into FOCONIS-test/dbarray-create-migration

commit 3a8f32b173
Author: Michael Benz <michael.benz@foconis.de>
Date:   Wed Apr 19 18:16:51 2017 +0200

    adding dbmigration ddlgeneration test for Ebean @DbArray extension.
2017-05-20 23:40:02 +12:00
rob bygrave 79f7c73983 #999 - Refactor tidy for ... @DiscriminatorColumn columnDefinition is ignored for DDL generation 2017-05-20 15:22:06 +12:00
rob bygrave d04954e46f #999 - @DiscriminatorColumn columnDefinition is ignored for DDL generation 2017-05-20 15:03:21 +12:00
rob bygrave 88c6ec358c #981 Additional refactor to 981 - use composed key in multi tenant environment - Expose TenantAwareKey handling for plugins 2017-05-20 13:53:46 +12:00
Roland PramlandRob Bygrave d3ea01b76e use composed key in multi tenant environment instead a cache per tenant (#981) 2017-05-20 12:35:34 +12:00
rob bygrave 0c1a9786a3 #996 Add findOne() and findOneOrEmpty() - SqlQuery 2017-05-19 23:48:41 +12:00
rob bygrave db298a349e #996 Add findOne() and findOneOrEmpty() 2017-05-19 23:38:40 +12:00
rob bygrave c1bed9f578 #996 Add findOne() and findOneOrEmpty() 2017-05-19 23:38:24 +12:00
rob bygrave c7cbe4ead8 Read the new @Length annotation 2017-05-19 23:00:10 +12:00
rob bygrave 4064aaf714 #1004 - Ebean migrations can not be executed if they contain ';' into a string 2017-05-15 22:18:24 +12:00
rob bygrave 6ad808003a Refactor - remove unused ebeandeploy.dtd 2017-05-14 21:11:00 +12:00
rob bygrave 70de6101db Refactor, remove unused SqlReservedWords 2017-05-14 21:10:24 +12:00
Roland PramlandRob Bygrave 933595ceea ADD: Testcases for "fetchDistinct" (#1024)
* ADD: Testcases for "fetchDistinct"

* Updated the tests for #1024
2017-05-14 17:39:07 +12:00
rob bygrave 461c2aef60 #946 - Treat primitive numbers (long, int, short, float, double) as NOT NULL wrt DDL generation 2017-05-14 01:31:28 +12:00
Rob Bygrave 0c49b6514c [maven-release-plugin] prepare for next development iteration 2017-05-11 23:54:47 +12:00
Rob Bygrave 0ec425928b [maven-release-plugin] prepare release ebean-10.2.3 2017-05-11 23:54:33 +12:00
rob bygrave 711701932a #1023 - Bug with findSingleAttributeList() when used against @ManyToOne / foreign key 2017-05-11 23:20:19 +12:00
Koen De GrooteandRob Bygrave 951e517caf Replaced Tail Recursion while while/true loops. (#1019) 2017-05-11 22:07:10 +12:00
rob bygrave 8cef603d1c #1021 Fix for query plan clash with single inheritance and cached query 2017-05-11 21:57:24 +12:00
Rien Nentjes 2a64a02211 Discriminator query cache error 2017-05-04 14:16:38 +02:00
Rob Bygrave 67ef0cd5d9 Refactor - repackage / move test code only 2017-05-01 20:39:49 +12:00
Rob Bygrave 9f2334362a [maven-release-plugin] prepare for next development iteration 2017-04-30 23:15:34 +12:00
Rob Bygrave 44ff304423 [maven-release-plugin] prepare release ebean-10.2.2 2017-04-30 23:15:20 +12:00
Roland PramlandRob Bygrave f9c70d2a2b Fix/select distinct cascaded fetch (#1015)
Fix/select distinct cascaded fetch with findSingleAttributeList() on associated property path
2017-04-30 23:07:35 +12:00
Rob Bygrave e51f7f11f1 Fix tests for #1016 2017-04-30 22:56:07 +12:00
Yuan XueguiandRob Bygrave 54ef0e7465 FIX BUG:and blank space after 1=1 (#1016)
FIX BUG:and blank space after 1=1 (Noop expression for equals null with @SoftDelete)
2017-04-30 22:50:03 +12:00
OndrejPacayandRob Bygrave 63def00626 #1012 - Filtering on embedded values in a joined table does not produce a sql join (#1013) 2017-04-30 22:33:02 +12:00
Koen De GrooteandRob Bygrave 335c4de109 Static code analysis. (#1017) 2017-04-30 14:47:02 +12:00
Rob Bygrave a120c2aaee #947 - Treat primitive boolean as NOT NULL - DDL as not null 2017-04-25 13:17:58 +12:00
Koen De GrooteandRob Bygrave 43408fcb87 Java language level updates concerning Maps. (#1008)
* Java language level updates concerning Maps.

* Reverts when comments indicated no sync can happen.
2017-04-22 15:48:46 +12:00
Koen De GrooteandRob Bygrave 8432f7b0d5 Some changes that are possible in Java language level 7 and 8. (#1007) 2017-04-22 15:43:56 +12:00
Koen De GrooteandRob Bygrave bdd7884f42 Changes for ArrayStack. (#1006)
Removed old inspection suppression.
2017-04-22 15:42:25 +12:00
Alexander WinklerandRob Bygrave 485e62b5d6 Added multi tenant catalog support. (#987) 2017-04-01 00:41:08 +13:00
Roland PramlandRob Bygrave 0557e6cd08 Wrong alter table / alter column DDL was generated (#988)
* FIX: alter column type uses correct DB-type now.

* FIX: Oracle test case
2017-04-01 00:38:23 +13:00
Koen De GrooteandRob Bygrave b821702b8d Moving defaultservercache implementation to use nanoseconds to determine whether or not something needs to be evicted from the cache. (#998)
The old implementation used System.currentTimeMillis(), which might be unreliable, for instance during a jump in DST.
Thanks @rPraml for noticing and suggesting the fix.
2017-04-01 00:30:01 +13:00
Rob Bygrave c5b23c321e #1002 - Bump ebean-dbmigration to 10.1.6 (better support for schema & sql server) 2017-04-01 00:28:46 +13:00
Rob Bygrave 964d2d00a2 #1001 - Add @Length and @NotNull, (plus move @Transactional to ebean-annotation) 2017-03-31 21:56:30 +13:00
Rob Bygrave 1778afa17d [maven-release-plugin] prepare for next development iteration 2017-03-13 23:15:12 +13:00
850 changed files with 14830 additions and 6606 deletions
+2 -1
View File
@@ -1,3 +1,4 @@
dist: trusty
sudo: required
language: java
jdk:
@@ -7,7 +8,7 @@ git:
depth: 3
addons:
postgresql: "9.4"
postgresql: "9.6"
services:
- postgresql
+152
View File
@@ -0,0 +1,152 @@
This is a short description how to run tests
============================================
Run tests from maven
--------------------
Just type `mvn test` - it will run the whole test suite with the settings
from `src/test/resources/ebean.properties`.
Run tests from eclipse
----------------------
Running tests requires, that the classes are enhanced. By default, the
`BaseTestCase` tries to load the enhancer-agent in a very early stage. This
also works for most test cases, but it does not work if you try to run a
complete test-suite, as classes that are loaded before the enhancer-agent
kicks in will not get enhanced. To solve this problem, you need either the
[Eclipse-Enhancer-Plugin](https://github.com/ebean-orm-tools/ebean-eclipse-enhancer)
or add the agent to your JVM arguments (Preferences->Installed JREs) or to
your run configuration:
`-javaagent:<USER_HOME>/.m2/repository/io/ebean/ebean-agent/<VERSION>/ebean-agent-<VERSION>.jar=debug=0`
Platform Tests
==============
By default, the `h2` platform is used. To test a different platform, add
`-Ddatasource.default=xxx` to your maven command or JVM arguments.
'h2' platform
-------------
Reqires no setup
run: `mvn clean test`
Current status: PASS
Tests run: 1986, Failures: 0, Errors: 0, Skipped: 16
'pg' platform
-------------
Reqirements
- a locally installed PostgreSQL server with PostGis.
- Create a user "unit" with password "unit" and a database "unit"
- Give the user all permissions to that db
run: `mvn clean test -Ddatasource.default=pg`
Current status: PASS
Tests run: 1986, Failures: 0, Errors: 0, Skipped: 16
'mysql' platform
-------------
Reqirements
- a locally installed MySQL server.
- Create a user "unit" with password "unit" and a database "unit"
- Give the user all permissions to that db
run: `mvn clean test -Ddatasource.default=mysql`
Current status: FAIL
Tests run: 1986, Failures: 2, Errors: 4, Skipped: 16
'sqlserver' platform
--------------------
Reqires an installed sqlserver - e.g. https://hub.docker.com/r/microsoft/mssql-server-linux/
- Create a user "ebean" with password "ebean" and a database "ebean_unittest"
- Adjust the connection string and/or username in ebean.properties
run: `mvn clean test -Ddatasource.default=mssql`
Current status: FAIL
Tests run: 1986, Failures: 19, Errors: 43, Skipped: 16
'oracle' platform
-----------------
Reqires a locally installed Oracle server.
- Create a user "unit" with password "unit" with all permissions
- The test wirtes directly to the "xe" SID - so do not use a productive server!
- As the Oracle JDBC driver is only available on an oracle repository,
you need a special maven setup as described [here](http://docs.oracle.com/middleware/1213/core/MAVEN/config_maven_repo.htm#MAVEN9010)
run: `mvn clean test -Ddatasource.default=ora -Poracle`
Current status: FAIL
Tests run: 1986, Failures: 17, Errors: 34, Skipped: 16
'db2' platform
--------------
Reqires a locally installed DB2 Express-C server.
- Create a user "unit" with password "unit" with all permissions
- Install the db2jcc4 driver (see pom.xml)
run: `mvn clean test -Ddatasource.default=db2 -Pdb2`
Current status: FAIL
Tests run: 1986, Failures: 33, Errors: 26, Skipped: 16
'sqlite' platform
-----------------
Reqires no setup
run: `mvn clean test -Ddatasource.default=sqlite`
Current status: FAIL
Tests run: 1986, Failures: 24, Errors: 86, Skipped: 16
After some time, the db locks up, every test takes 3 seconds and fails with
"the database file is locked"
'hsqldb' platform
-----------------
Reqires no setup
run: `mvn clean test -Ddatasource.default=hsqldb`
Current status: FAIL
Tests run: 1980, Failures: 3, Errors: 657, Skipped: 16
@Rob FYI
The problem is https://sourceforge.net/p/hsqldb/bugs/1364/
In `InsertHandler.getPstmt` the `meta.getIdentityDbColumns()` contains
'id' in lowercase. Changing that to uppercase gives me this result:
`Tests run: 1986, Failures: 2, Errors: 18, Skipped: 16`
(as quick hack I added temporary a toUpperCase to BindableIdScalar.getIdentityColumn)
'sqlanywhere' platform
----------------------
TODO: not yet tested
+72 -14
View File
@@ -9,7 +9,7 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>10.2.1</version>
<version>11.2.2</version>
<packaging>jar</packaging>
<name>ebean</name>
@@ -17,9 +17,59 @@
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-10.2.1</tag>
<tag>ebean-11.2.2</tag>
</scm>
<profiles>
<profile>
<!-- Note: to use this profile, you need credentials for the oracle repository in your
settings.xml as described here:
http://docs.oracle.com/middleware/1213/core/MAVEN/config_maven_repo.htm#MAVEN9010 -->
<id>oracle</id>
<dependencies>
<dependency>
<groupId>com.oracle.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
<version>12.2.0.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>maven.oracle.com</id>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
<url>https://maven.oracle.com</url>
<layout>default</layout>
</repository>
</repositories>
</profile>
<profile>
<!-- Note: to use this profile, you need to download manually the db2jcc4 driver.
After that, install it into your local maven repository:
mvn install:install-file \
-Dfile=db2jcc4.jar \
-DgroupId=com.ibm.jdbc \
-DartifactId=db2jcc4 \
-Dversion=4.23.42 \
-Dpackaging=jar
-->
<id>db2</id>
<dependencies>
<dependency>
<groupId>com.ibm.jdbc</groupId>
<artifactId>db2jcc4</artifactId>
<version>4.23.42</version>
<scope>test</scope>
</dependency>
</dependencies>
</profile>
</profiles>
<dependencies>
<dependency>
@@ -37,7 +87,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-annotation</artifactId>
<version>2.2</version>
<version>3.1</version>
</dependency>
<dependency>
@@ -49,7 +99,7 @@
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-datasource</artifactId>
<version>2.1.1</version>
<version>2.1.2</version>
</dependency>
<dependency>
@@ -61,13 +111,13 @@
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-classpath-scanner</artifactId>
<version>2.2.2</version>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-dbmigration</artifactId>
<version>10.1.4</version>
<artifactId>ebean-migration</artifactId>
<version>10.3.1</version>
</dependency>
<dependency>
@@ -86,14 +136,14 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.6.5</version>
<version>2.9.0</version>
</dependency>
<!-- provided scope for JsonNode support -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.6.5</version>
<version>2.9.0</version>
<scope>provided</scope>
</dependency>
@@ -130,7 +180,7 @@
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.4.1212.jre7</version>
<version>9.4.1212</version>
<scope>provided</scope>
</dependency>
@@ -160,7 +210,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-agent</artifactId>
<version>10.1.7</version>
<version>11.1.1</version>
<scope>test</scope>
</dependency>
@@ -187,10 +237,18 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>6.1.3.jre8-preview</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.5</version>
<!-- Do not upgrade to 6.0.6: https://bugs.mysql.com/bug.php?id=82896 -->
<version>5.1.43</version>
<scope>test</scope>
</dependency>
@@ -225,7 +283,7 @@
<plugin>
<groupId>io.ebean</groupId>
<artifactId>ebean-maven-plugin</artifactId>
<version>10.1.7</version>
<version>11.1.1</version>
<executions>
<execution>
<id>test</id>
@@ -282,7 +340,7 @@
<overview>src/main/java/io/ebean/overview.html</overview>
<source>1.8</source>
<doclet>org.avaje.doclet.PygmentsDoclet</doclet>
<excludePackageNames>io.ebeaninternal.*:com.avaje.ebean.util:io.ebean.dbmigration.ddlgeneration:io.ebean.dbmigration.migration:io.ebean.dbmigration.migrationreader:io.ebean.dbmigration.model</excludePackageNames>
<excludePackageNames>io.ebeaninternal.*:com.avaje.ebean.util</excludePackageNames>
<docletArtifact>
<groupId>org.avaje</groupId>
<artifactId>pygments-doclet</artifactId>
@@ -10,6 +10,7 @@ import javax.persistence.PessimisticLockException;
* </p>
*/
public class AcquireLockException extends PessimisticLockException {
private static final long serialVersionUID = -8585962352965876691L;
/**
* Create with a message and cause.
+57
View File
@@ -0,0 +1,57 @@
package io.ebean;
/**
* Enum to control the different cache modes for queryCache (and maybe later) beanCache.
*
* If cache is enabled, you must be careful, what you do with the returned collection.
* By default the returned collections are read only and you will get an exception if you try
* to change them.
* If you add ".setReadOnly(false)" to your query, you'll get a collection that is a clone from the
* one in the cache. That means, changing does not affect the cache
*
* @author Roland Praml, FOCONIS AG
*/
public enum CacheMode {
/**
* Do not use cache.
*/
OFF(false, false),
/**
* Use the cace (query & store the resut).
*/
ON(true, true),
/**
* Do not read from cache, but write retrived value to cache.
* Use this, if you want to get the fresh value from database and a CacheMode.ON query will follow.
*/
RECACHE(false, true),
/**
* Query the cache for value. If it is there, use it, otherwise hit database but do NOT put the value
* into the cache. (this mode is for completeness. There's probably no use case for this)
*/
QUERY_ONLY(true,false);
private boolean get;
private boolean put;
private CacheMode(boolean get, boolean put) {
this.get = get;
this.put = put;
}
/**
* Retruns <code>true</code> if value is read from cache.
*/
public boolean isGet() {
return get;
}
/**
* Returns <code>true</code> if value (from database) is written to cache.
*/
public boolean isPut() {
return put;
}
}
@@ -6,6 +6,7 @@ import javax.persistence.PersistenceException;
* Thrown when a foreign key constraint is enforced.
*/
public class DataIntegrityException extends PersistenceException {
private static final long serialVersionUID = -6740171949170180970L;
/**
* Create with a message and cause.
+2 -2
View File
@@ -56,7 +56,7 @@ public interface DocumentStore {
* Return the bean by fetching it's content from the document store.
* If the document is not found null is returned.
* <p>
* Typically this is called indirectly by findUnique() on the query.
* Typically this is called indirectly by findOne() on the query.
* </p>
* <pre>{@code
*
@@ -64,7 +64,7 @@ public interface DocumentStore {
* server.find(Customer.class)
* .setUseDocStore(true)
* .setId(42)
* .findUnique();
* .findOne();
*
* }</pre>
*/
@@ -4,6 +4,7 @@ package io.ebean;
* Thrown when a duplicate is attempted on a unique constraint.
*/
public class DuplicateKeyException extends DataIntegrityException {
private static final long serialVersionUID = -4771932723285724817L;
/**
* Create with a message and cause.
+29 -25
View File
@@ -1,5 +1,6 @@
package io.ebean;
import io.ebean.annotation.TxIsolation;
import io.ebean.cache.ServerCacheManager;
import io.ebean.config.ServerConfig;
import io.ebean.text.csv.CsvReader;
@@ -14,6 +15,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -154,7 +156,7 @@ public final class Ebean {
} else {
// look to see if there is a default server defined
String defaultName = PrimaryServer.getDefaultServerName();
logger.debug("defaultName:" + defaultName);
logger.debug("defaultName:{}", defaultName);
if (defaultName != null && !defaultName.trim().isEmpty()) {
defaultServer = getWithCreate(defaultName.trim());
}
@@ -289,7 +291,7 @@ public final class Ebean {
}
/**
* Backdoor for registering a mock implementation of EbeanServer as the default server.
* Backdoor for registering a mock implementation of EbeanServer as the default server.
*/
protected static EbeanServer mock(String name, EbeanServer server, boolean defaultServer) {
EbeanServer originalPrimaryServer = serverMgr.defaultServer;
@@ -371,9 +373,10 @@ public final class Ebean {
/**
* Start a transaction typically specifying REQUIRES_NEW or REQUIRED semantics.
* <p>
* Note that this provides an try finally alternative to using {@link #execute(TxScope, TxCallable)} or
* {@link #execute(TxScope, TxRunnable)}.
* Note that this provides an try finally alternative to using {@link #executeCall(TxScope, Callable)} or
* {@link #execute(TxScope, Runnable)}.
* </p>
*
* <h3>REQUIRES_NEW example:</h3>
* <pre>{@code
* // Start a new transaction. If there is a current transaction
@@ -822,7 +825,7 @@ public final class Ebean {
* }</pre>
* <p>
* If you want more control over the query then you can use createQuery() and
* Query.findUnique();
* Query.findOne();
* </p>
* <pre>{@code
*
@@ -843,7 +846,8 @@ public final class Ebean {
*
* // traverse the object graph...
*
* Order order = query.findUnique();
* Order order = query.findOne();
*
* Customer customer = order.getCustomer();
* Address shippingAddress = customer.getShippingAddress();
* List<OrderDetail> details = order.getDetails();
@@ -959,7 +963,7 @@ public final class Ebean {
* predicates, order by, limits etc.
* </p>
* <p>
* You then use findList(), findSet(), findMap() and findUnique() to execute
* You then use findList(), findSet(), findMap() and findOne() to execute
* the query and return the collection or bean.
* </p>
* <p>
@@ -991,7 +995,7 @@ public final class Ebean {
* Query<Order> query = Ebean.createQuery(Order.class, eql);
* query.setParameter("orderId", 2);
*
* Order order = query.findUnique();
* Order order = query.findOne();
*
* // This is the same as:
*
@@ -1000,7 +1004,7 @@ public final class Ebean {
* .fetch("details")
* .fetch("detail.product", "name")
* .setId(2)
* .findUnique();
* .findOne();
*
* }</pre>
*
@@ -1181,20 +1185,20 @@ public final class Ebean {
*
* }</pre>
*/
public static void execute(TxScope scope, TxRunnable r) {
public static void execute(TxScope scope, Runnable r) {
serverMgr.getDefaultServer().execute(scope, r);
}
/**
* Execute a TxRunnable in a Transaction with the default scope.
* Execute a Runnable in a Transaction with the default scope.
* <p>
* The default scope runs with REQUIRED and by default will rollback on any
* exception (checked or runtime).
* </p>
* <pre>{@code
*
* Ebean.execute(new TxRunnable() {
* public void run() {
* Ebean.execute(() -> {
*
* User u1 = Ebean.find(User.class, 1);
* User u2 = Ebean.find(User.class, 2);
*
@@ -1203,17 +1207,17 @@ public final class Ebean {
*
* Ebean.save(u1);
* Ebean.save(u2);
* }
*
* });
*
* }</pre>
*/
public static void execute(TxRunnable r) {
public static void execute(Runnable r) {
serverMgr.getDefaultServer().execute(r);
}
/**
* Execute a TxCallable in a Transaction with an explicit scope.
* Execute a Callable in a Transaction with an explicit scope.
* <p>
* The scope can control the transaction type, isolation and rollback
* semantics.
@@ -1223,7 +1227,7 @@ public final class Ebean {
* // set specific transactional scope settings
* TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
*
* Ebean.execute(scope, new TxCallable<String>() {
* Ebean.executeCall(scope, new Callable<String>() {
* public String call() {
* User u1 = Ebean.find(User.class, 1);
* ...
@@ -1233,12 +1237,12 @@ public final class Ebean {
*
* }</pre>
*/
public static <T> T execute(TxScope scope, TxCallable<T> c) {
return serverMgr.getDefaultServer().execute(scope, c);
public static <T> T executeCall(TxScope scope, Callable<T> c) {
return serverMgr.getDefaultServer().executeCall(scope, c);
}
/**
* Execute a TxCallable in a Transaction with the default scope.
* Execute a Callable in a Transaction with the default scope.
* <p>
* The default scope runs with REQUIRED and by default will rollback on any
* exception (checked or runtime).
@@ -1249,8 +1253,8 @@ public final class Ebean {
* </p>
* <pre>{@code
*
* Ebean.execute(new TxCallable<String>() {
* public String call() {
* Ebean.executeCall(() -> {
*
* User u1 = Ebean.find(User.class, 1);
* User u2 = Ebean.find(User.class, 2);
*
@@ -1261,13 +1265,13 @@ public final class Ebean {
* Ebean.save(u2);
*
* return u1.getEmail();
* }
*
* });
*
* }</pre>
*/
public static <T> T execute(TxCallable<T> c) {
return serverMgr.getDefaultServer().execute(c);
public static <T> T executeCall(Callable<T> c) {
return serverMgr.getDefaultServer().executeCall(c);
}
/**
+60 -30
View File
@@ -1,5 +1,6 @@
package io.ebean;
import io.ebean.annotation.TxIsolation;
import io.ebean.cache.ServerCacheManager;
import io.ebean.config.ServerConfig;
import io.ebean.meta.MetaInfoManager;
@@ -14,7 +15,9 @@ import javax.persistence.PersistenceException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -237,7 +240,7 @@ public interface EbeanServer {
* Query<Order> query = Ebean.createQuery(Order.class, ormQuery);
* query.setParameter("orderId", 2);
*
* Order order = query.findUnique();
* Order order = query.findOne();
*
* // This is the same as:
*
@@ -246,7 +249,7 @@ public interface EbeanServer {
* .fetch("details")
* .fetch("detail.product", "name")
* .setId(2)
* .findUnique();
* .findOne();
*
* }</pre>
*
@@ -264,7 +267,7 @@ public interface EbeanServer {
* predicates, order by, limits etc.
* </p>
* <p>
* You then use findList(), findSet(), findMap() and findUnique() to execute
* You then use findList(), findSet(), findMap() and findOne() to execute
* the query and return the collection or bean.
* </p>
* <p>
@@ -284,7 +287,7 @@ public interface EbeanServer {
* .fetch("details")
* .fetch("detail.product", "name")
* .setId(2)
* .findUnique();
* .findOne();
*
* // find some new orders ... with firstRow/maxRows
* List<Order> orders =
@@ -559,8 +562,8 @@ public interface EbeanServer {
* Start a transaction typically specifying REQUIRES_NEW or REQUIRED semantics.
* <p>
* <p>
* Note that this provides an try finally alternative to using {@link #execute(TxScope, TxCallable)} or
* {@link #execute(TxScope, TxRunnable)}.
* Note that this provides an try finally alternative to using {@link #executeCall(TxScope, Callable)} or
* {@link #execute(TxScope, Runnable)}.
* </p>
* <p>
* <h3>REQUIRES_NEW example:</h3>
@@ -693,7 +696,7 @@ public interface EbeanServer {
* <p>
* <p>
* If you want more control over the query then you can use createQuery() and
* Query.findUnique();
* Query.findOne();
* </p>
* <p>
* <pre>{@code
@@ -713,7 +716,7 @@ public interface EbeanServer {
* query.fetch("details.product", "name");
*
*
* Order order = query.findUnique();
* Order order = query.findOne();
*
* // traverse the object graph...
*
@@ -1066,7 +1069,7 @@ public interface EbeanServer {
* This will throw a NonUniqueResultException if the query finds more than one result.
* </p>
* <p>
* Generally you are able to use {@link Query#findUnique()} rather than
* Generally you are able to use {@link Query#findOne()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
@@ -1076,10 +1079,28 @@ public interface EbeanServer {
* @param transaction the transaction to use (can be null).
* @return the list of fetched beans.
* @throws NonUniqueResultException if more than one result was found
* @see Query#findUnique()
* @see Query#findOne()
*/
@Nullable
<T> T findUnique(Query<T> query, Transaction transaction);
<T> T findOne(Query<T> query, Transaction transaction);
/**
* Similar to findOne() but returns an Optional (rather than nullable).
*/
<T> Optional<T> findOneOrEmpty(Query<T> query, Transaction transaction);
/**
* Deprecated - please migrate to findOne().
* <p>
* This proceeded findOne which was introduced to better match spring data.
* This will be deprecated at some future point.
* </p>
*/
@Deprecated
@Nullable
default <T> T findUnique(Query<T> query, Transaction transaction) {
return findOne(query, transaction);
}
/**
* Execute as a delete query deleting the 'root level' beans that match the predicates
@@ -1150,7 +1171,7 @@ public interface EbeanServer {
* result.
* </p>
* <p>
* Generally you are able to use {@link SqlQuery#findUnique()} rather than
* Generally you are able to use {@link SqlQuery#findOne()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
@@ -1158,10 +1179,23 @@ public interface EbeanServer {
* @param query the query to execute.
* @param transaction the transaction to use (can be null).
* @return the fetched MapBean or null if none was found.
* @see SqlQuery#findUnique()
* @see SqlQuery#findOne()
*/
@Nullable
SqlRow findUnique(SqlQuery query, Transaction transaction);
SqlRow findOne(SqlQuery query, Transaction transaction);
/**
* Deprecated - please migrate to findOne().
* <p>
* This proceeded findOne which was introduced to better match spring data.
* This will be deprecated at some future point.
* </p>
*/
@Deprecated
@Nullable
default SqlRow findUnique(SqlQuery query, Transaction transaction) {
return findOne(query, transaction);
}
/**
* Either Insert or Update the bean depending on its state.
@@ -1559,7 +1593,7 @@ public interface EbeanServer {
int execute(CallableSql callableSql, Transaction transaction);
/**
* Execute a TxRunnable in a Transaction with an explicit scope.
* Execute a Runnable in a Transaction with an explicit scope.
* <p>
* The scope can control the transaction type, isolation and rollback
* semantics.
@@ -1570,7 +1604,7 @@ public interface EbeanServer {
* // set specific transactional scope settings
* TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
*
* ebeanServer.execute(scope, new TxRunnable() {
* ebeanServer.execute(scope, new Runnable() {
* public void run() {
* User u1 = Ebean.find(User.class, 1);
* ...
@@ -1579,10 +1613,10 @@ public interface EbeanServer {
*
* }</pre>
*/
void execute(TxScope scope, TxRunnable runnable);
void execute(TxScope scope, Runnable runnable);
/**
* Execute a TxRunnable in a Transaction with the default scope.
* Execute a Runnable in a Transaction with the default scope.
* <p>
* The default scope runs with REQUIRED and by default will rollback on any
* exception (checked or runtime).
@@ -1590,8 +1624,8 @@ public interface EbeanServer {
* <p>
* <pre>{@code
*
* ebeanServer.execute(new TxRunnable() {
* public void run() {
* ebeanServer.execute(() -> {
*
* User u1 = ebeanServer.find(User.class, 1);
* User u2 = ebeanServer.find(User.class, 2);
*
@@ -1600,12 +1634,12 @@ public interface EbeanServer {
*
* ebeanServer.save(u1);
* ebeanServer.save(u2);
* }
*
* });
*
* }</pre>
*/
void execute(TxRunnable runnable);
void execute(Runnable runnable);
/**
* Execute a TxCallable in a Transaction with an explicit scope.
@@ -1619,7 +1653,7 @@ public interface EbeanServer {
* // set specific transactional scope settings
* TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
*
* ebeanServer.execute(scope, new TxCallable<String>() {
* ebeanServer.executeCall(scope, new Callable<String>() {
* public String call() {
* User u1 = ebeanServer.find(User.class, 1);
* ...
@@ -1629,7 +1663,7 @@ public interface EbeanServer {
*
* }</pre>
*/
<T> T execute(TxScope scope, TxCallable<T> callable);
<T> T executeCall(TxScope scope, Callable<T> callable);
/**
* Execute a TxCallable in a Transaction with the default scope.
@@ -1638,13 +1672,9 @@ public interface EbeanServer {
* exception (checked or runtime).
* </p>
* <p>
* This is basically the same as TxRunnable except that it returns an Object
* (and you specify the return type via generics).
* </p>
* <p>
* <pre>{@code
*
* ebeanServer.execute(new TxCallable<String>() {
* ebeanServer.executeCall(new Callable<String>() {
* public String call() {
* User u1 = ebeanServer.find(User.class, 1);
* User u2 = ebeanServer.find(User.class, 2);
@@ -1661,7 +1691,7 @@ public interface EbeanServer {
*
* }</pre>
*/
<T> T execute(TxCallable<T> callable);
<T> T executeCall(Callable<T> callable);
/**
* Return the manager of the server cache ("L2" cache).
+9 -14
View File
@@ -1,13 +1,14 @@
package io.ebean;
import io.ebean.common.SpiContainer;
import io.ebean.config.ContainerConfig;
import io.ebean.config.ServerConfig;
import io.ebeaninternal.server.lib.ShutdownManager;
import io.ebean.service.SpiContainer;
import io.ebean.service.SpiContainerFactory;
import javax.persistence.PersistenceException;
import java.lang.reflect.Constructor;
import java.util.Iterator;
import java.util.Properties;
import java.util.ServiceLoader;
/**
* Creates EbeanServer instances.
@@ -30,8 +31,6 @@ import java.util.Properties;
public class EbeanServerFactory {
private static final String DEFAULT_CONTAINER = "io.ebeaninternal.server.core.DefaultContainer";
private static SpiContainer container;
/**
@@ -99,7 +98,7 @@ public class EbeanServerFactory {
* </p>
*/
public static synchronized void shutdown() {
ShutdownManager.shutdown();
container.shutdown();
}
@@ -135,14 +134,10 @@ public class EbeanServerFactory {
*/
protected static SpiContainer createContainer(ContainerConfig containerConfig) {
String implClassName = System.getProperty("ebean.container", DEFAULT_CONTAINER);
try {
Class<?> cls = Class.forName(implClassName);
Constructor<?> constructor = cls.getConstructor(ContainerConfig.class);
return (SpiContainer) constructor.newInstance(containerConfig);
} catch (Exception ex) {
throw new RuntimeException(ex);
Iterator<SpiContainerFactory> factories = ServiceLoader.load(SpiContainerFactory.class).iterator();
if (factories.hasNext()) {
return factories.next().create(containerConfig);
}
throw new IllegalStateException("Service loader didn't find a SpiContainerFactory?");
}
}
@@ -7,7 +7,6 @@ import io.ebean.search.TextQueryString;
import io.ebean.search.TextSimple;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
@@ -308,9 +307,9 @@ public interface ExpressionFactory {
Expression idIn(Object... idValues);
/**
* Id IN a list of Id values.
* Id IN a collection of Id values.
*/
Expression idIn(List<?> idList);
Expression idIn(Collection<?> idCollection);
/**
* All Equal - Map containing property names and their values.
+34 -6
View File
@@ -12,6 +12,7 @@ import java.sql.Timestamp;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -263,10 +264,28 @@ public interface ExpressionList<T> {
* </p>
*
* @throws NonUniqueResultException if more than one result was found
* @see Query#findUnique()
* @see Query#findOne()
*/
@Nullable
T findUnique();
T findOne();
/**
* Execute the query returning an optional bean.
*/
Optional<T> findOneOrEmpty();
/**
* Deprecated - please migrate to findOne().
* <p>
* This proceeded findOne which was introduced to better match spring data.
* This will be deprecated at some future point.
* </p>
*/
@Deprecated
@Nullable
default T findUnique() {
return findOne();
}
/**
* Execute find row count query in a background thread.
@@ -426,12 +445,21 @@ public interface ExpressionList<T> {
Query<T> setUseCache(boolean useCache);
/**
* Set to true to use the query for executing this query.
* Set the {@link CacheMode} to use the query for executing this query.
*
* @see Query#setUseQueryCache(boolean)
*/
Query<T> setUseQueryCache(boolean useCache);
Query<T> setUseQueryCache(CacheMode useCache);
/**
* Calls {@link #setUseQueryCache(CacheMode)} with <code>ON</code> or <code>OFF</code>.
*
* @see Query#setUseQueryCache(CacheMode)
*/
default Query<T> setUseQueryCache(boolean enabled) {
return setUseQueryCache(enabled ? CacheMode.ON : CacheMode.OFF);
}
/**
* Set to true if this query should execute against the doc store.
* <p>
@@ -820,9 +848,9 @@ public interface ExpressionList<T> {
ExpressionList<T> idIn(Object... idValues);
/**
* Id IN a list of id values.
* Id IN a collection of id values.
*/
ExpressionList<T> idIn(List<?> idValues);
ExpressionList<T> idIn(Collection<?> idValues);
/**
* Id Equal to - ID property is equal to the value.
+16 -1
View File
@@ -27,7 +27,7 @@ import java.util.List;
* // Add your customer finder methods ...
*
* public Customer byName(String name) {
* return query().eq("name", name).findUnique();
* return query().eq("name", name).findOne();
* }
*
* public List<Customer> findNew() {
@@ -92,6 +92,20 @@ public class Finder<I, T> {
this.serverName = serverName;
}
/**
* Return the current transaction.
*/
public Transaction currentTransaction() {
return db().currentTransaction();
}
/**
* Flush the JDBC batch on the current transaction.
*/
public void flush() {
db().flush();
}
/**
* Return the underlying 'default' EbeanServer.
* <p>
@@ -170,4 +184,5 @@ public class Finder<I, T> {
public Query<T> query(String ormQuery) {
return db().createQuery(type, ormQuery);
}
}
+4 -4
View File
@@ -6,22 +6,22 @@ package io.ebean;
public enum LikeType {
/**
* You need to put in your own wildcards.
* You need to put in your own wildcards - no escaping is performed.
*/
RAW,
/**
* The % wildcard is added to the end of the search word.
* The % wildcard is added to the end of the search word and search word is escaped.
*/
STARTS_WITH,
/**
* The % wildcard is added to the beginning of the search word.
* The % wildcard is added to the beginning of the search word and search word is escaped.
*/
ENDS_WITH,
/**
* The % wildcard is added to the beginning and end of the search word.
* The % wildcard is added to the beginning and end of the search word and search word is escaped.
*/
CONTAINS,
+33 -12
View File
@@ -6,6 +6,7 @@ import javax.persistence.NonUniqueResultException;
import java.sql.Timestamp;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -214,11 +215,6 @@ public interface Query<T> {
SKIPLOCKED
}
/**
* Return the RawSql that was set to use for this query.
*/
RawSql getRawSql();
/**
* Set RawSql to use for this query.
*/
@@ -830,7 +826,7 @@ public interface Query<T> {
* Product product =
* ebeanServer.find(Product.class)
* .where().eq("sku", "aa113")
* .findUnique();
* .findOne();
* ...
* }</pre>
* <p>
@@ -844,7 +840,7 @@ public interface Query<T> {
* ebeanServer.find(Order.class)
* .setId(1)
* .fetch("details")
* .findUnique();
* .findOne();
*
* // the order details were eagerly loaded
* List<OrderDetail> details = order.getDetails();
@@ -854,7 +850,25 @@ public interface Query<T> {
* @throws NonUniqueResultException if more than one result was found
*/
@Nullable
T findUnique();
T findOne();
/**
* Execute the query returning an optional bean.
*/
Optional<T> findOneOrEmpty();
/**
* Deprecated - please migrate to findOne().
* <p>
* This proceeded findOne which was introduced to better match spring data.
* This will be deprecated at some future point.
* </p>
*/
@Deprecated
@Nullable
default T findUnique() {
return findOne();
}
/**
* Return versions of a @History entity bean.
@@ -1012,7 +1026,7 @@ public interface Query<T> {
Query<T> setParameter(int position, Object value);
/**
* Set the Id value to query. This is used with findUnique().
* Set the Id value to query. This is used with findOne().
* <p>
* You can use this to have further control over the query. For example adding
* fetch joins.
@@ -1023,7 +1037,7 @@ public interface Query<T> {
* ebeanServer.find(Order.class)
* .setId(1)
* .fetch("details")
* .findUnique();
* .findOne();
*
* // the order details were eagerly fetched
* List<OrderDetail> details = order.getDetails();
@@ -1306,9 +1320,16 @@ public interface Query<T> {
Query<T> setUseCache(boolean useCache);
/**
* Set this to true to use the query cache.
* Set the {@link CacheMode} to use the query for executing this query.
*/
Query<T> setUseQueryCache(boolean useQueryCache);
Query<T> setUseQueryCache(CacheMode useQueryCache);
/**
* Calls {@link #setUseQueryCache(CacheMode)} with <code>ON</code> or <code>OFF</code>.
*/
default Query<T> setUseQueryCache(boolean enabled) {
return setUseQueryCache(enabled ? CacheMode.ON : CacheMode.OFF);
}
/**
* Set to true if this query should execute against the doc store.
+1 -572
View File
@@ -1,16 +1,5 @@
package io.ebean;
import io.ebean.util.CamelCaseHelper;
import java.io.Serializable;
import java.sql.ResultSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Used to build object graphs based on a raw SQL statement (rather than
* generated by Ebean).
@@ -169,566 +158,6 @@ import java.util.Map;
* Note that lazy loading also works with object graphs built with RawSql.
* </p>
*/
public final class RawSql implements Serializable {
public interface RawSql {
private static final long serialVersionUID = 1L;
private final ResultSet resultSet;
private final Sql sql;
private final ColumnMapping columnMapping;
/**
* Construct with a ResultSet and properties that the columns map to.
* <p>
* The properties listed in the propertyNames must be in the same order as the columns in the
* resultSet.
* <p>
* When a query executes this RawSql object then it will close the resultSet.
*/
public RawSql(ResultSet resultSet, String... propertyNames) {
this.resultSet = resultSet;
this.sql = null;
this.columnMapping = new ColumnMapping(propertyNames);
}
protected RawSql(ResultSet resultSet, Sql sql, ColumnMapping columnMapping) {
this.resultSet = resultSet;
this.sql = sql;
this.columnMapping = columnMapping;
}
/**
* Return the Sql either unparsed or in parsed (broken up) form.
*/
public Sql getSql() {
return sql;
}
/**
* Return the key;
*/
public Key getKey() {
boolean parsed = sql != null && sql.parsed;
String unParsedSql = (sql == null) ? "" : sql.unparsedSql;
return new Key(parsed, unParsedSql, columnMapping);
}
/**
* Return the resultSet if this is a ResultSet based RawSql.
*/
public ResultSet getResultSet() {
return resultSet;
}
/**
* Return the column mapping for the SQL columns to bean properties.
*/
public ColumnMapping getColumnMapping() {
return columnMapping;
}
/**
* Represents the sql part of the query. For parsed RawSql the sql is broken
* up so that Ebean can insert extra WHERE and HAVING expressions into the
* SQL.
*/
public static final class Sql implements Serializable {
private static final long serialVersionUID = 1L;
private final boolean parsed;
private final String unparsedSql;
private final String preFrom;
private final String preWhere;
private final boolean andWhereExpr;
private final String preHaving;
private final boolean andHavingExpr;
private final String orderByPrefix;
private final String orderBy;
private final boolean distinct;
/**
* Construct for unparsed SQL.
*/
protected Sql(String unparsedSql) {
this.parsed = false;
this.unparsedSql = unparsedSql;
this.preFrom = null;
this.preHaving = null;
this.preWhere = null;
this.andHavingExpr = false;
this.andWhereExpr = false;
this.orderByPrefix = null;
this.orderBy = null;
this.distinct = false;
}
/**
* Construct for parsed SQL.
*/
protected Sql(String unparsedSql, String preFrom, String preWhere, boolean andWhereExpr,
String preHaving, boolean andHavingExpr, String orderByPrefix, String orderBy, boolean distinct) {
this.unparsedSql = unparsedSql;
this.parsed = true;
this.preFrom = preFrom;
this.preHaving = preHaving;
this.preWhere = preWhere;
this.andHavingExpr = andHavingExpr;
this.andWhereExpr = andWhereExpr;
this.orderByPrefix = orderByPrefix;
this.orderBy = orderBy;
this.distinct = distinct;
}
@Override
public String toString() {
if (!parsed) {
return "unparsed[" + unparsedSql + "]";
}
return "select[" + preFrom + "] preWhere[" + preWhere + "] preHaving[" + preHaving + "] orderBy[" + orderBy + "]";
}
public boolean isDistinct() {
return distinct;
}
/**
* Return true if the SQL is left completely unmodified.
* <p>
* This means Ebean can't add WHERE or HAVING expressions into the query -
* it will be left completely unmodified.
* </p>
*/
public boolean isParsed() {
return parsed;
}
/**
* Return the SQL when it is unparsed.
*/
public String getUnparsedSql() {
return unparsedSql;
}
/**
* Return the SQL prior to FROM clause.
*/
public String getPreFrom() {
return preFrom;
}
/**
* Return the SQL prior to WHERE clause.
*/
public String getPreWhere() {
return preWhere;
}
/**
* Return true if there is already a WHERE clause and any extra where
* expressions start with AND.
*/
public boolean isAndWhereExpr() {
return andWhereExpr;
}
/**
* Return the SQL prior to HAVING clause.
*/
public String getPreHaving() {
return preHaving;
}
/**
* Return true if there is already a HAVING clause and any extra having
* expressions start with AND.
*/
public boolean isAndHavingExpr() {
return andHavingExpr;
}
/**
* Return the 'order by' keywords.
* This can contain additional keywords, for example 'order siblings by' as Oracle syntax.
*/
public String getOrderByPrefix() {
return (orderByPrefix == null) ? "order by" : orderByPrefix;
}
/**
* Return the SQL ORDER BY clause.
*/
public String getOrderBy() {
return orderBy;
}
}
/**
* Defines the column mapping for raw sql DB columns to bean properties.
*/
public static final class ColumnMapping implements Serializable {
private static final long serialVersionUID = 1L;
private final LinkedHashMap<String, Column> dbColumnMap;
private final Map<String, String> propertyMap;
private final Map<String, Column> propertyColumnMap;
private final boolean parsed;
private final boolean immutable;
/**
* Construct from parsed sql where the columns have been identified.
*/
protected ColumnMapping(List<Column> columns) {
this.immutable = false;
this.parsed = true;
this.propertyMap = null;
this.propertyColumnMap = null;
this.dbColumnMap = new LinkedHashMap<>();
for (Column c : columns) {
dbColumnMap.put(c.getDbColumnKey(), c);
}
}
/**
* Construct for unparsed sql.
*/
protected ColumnMapping() {
this.immutable = false;
this.parsed = false;
this.propertyMap = null;
this.propertyColumnMap = null;
this.dbColumnMap = new LinkedHashMap<>();
}
/**
* Construct for ResultSet use.
*/
protected ColumnMapping(String... propertyNames) {
this.immutable = false;
this.parsed = false;
this.propertyMap = null;
this.dbColumnMap = new LinkedHashMap<>();
int pos = 0;
for (String prop : propertyNames) {
dbColumnMap.put(prop, new Column(pos++, prop, null, prop));
}
propertyColumnMap = dbColumnMap;
}
/**
* Construct an immutable ColumnMapping based on collected information.
*/
protected ColumnMapping(boolean parsed, LinkedHashMap<String, Column> dbColumnMap) {
this.immutable = true;
this.parsed = parsed;
this.dbColumnMap = dbColumnMap;
HashMap<String, Column> pcMap = new HashMap<>();
HashMap<String, String> pMap = new HashMap<>();
for (Column c : dbColumnMap.values()) {
pMap.put(c.getPropertyName(), c.getDbColumn());
pcMap.put(c.getPropertyName(), c);
}
this.propertyMap = Collections.unmodifiableMap(pMap);
this.propertyColumnMap = Collections.unmodifiableMap(pcMap);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ColumnMapping that = (ColumnMapping) o;
return dbColumnMap.equals(that.dbColumnMap);
}
@Override
public int hashCode() {
return dbColumnMap.hashCode();
}
/**
* Return true if the property is mapped.
*/
public boolean contains(String property) {
return this.propertyColumnMap.containsKey(property);
}
/**
* Creates an immutable copy of this ColumnMapping.
*
* @throws IllegalStateException when a propertyName has not been defined for a column.
*/
protected ColumnMapping createImmutableCopy() {
for (Column c : dbColumnMap.values()) {
c.checkMapping();
}
return new ColumnMapping(parsed, dbColumnMap);
}
protected void columnMapping(String dbColumn, String propertyName) {
if (immutable) {
throw new IllegalStateException("Should never happen");
}
if (!parsed) {
int pos = dbColumnMap.size();
dbColumnMap.put(dbColumn, new Column(pos, dbColumn, null, propertyName));
} else {
Column column = dbColumnMap.get(dbColumn);
if (column == null) {
String msg = "DB Column [" + dbColumn + "] not found in mapping. Expecting one of [" + dbColumnMap.keySet() + "]";
throw new IllegalArgumentException(msg);
}
column.setPropertyName(propertyName);
}
}
/**
* Returns true if the Columns where supplied by parsing the sql select
* clause.
* <p>
* In the case where the columns where parsed then we can do extra checks on
* the column mapping such as, is the column a valid one in the sql and
* whether all the columns in the sql have been mapped.
* </p>
*/
public boolean isParsed() {
return parsed;
}
/**
* Return the number of columns in this column mapping.
*/
public int size() {
return dbColumnMap.size();
}
/**
* Return the column mapping.
*/
protected Map<String, Column> mapping() {
return dbColumnMap;
}
/**
* Return the mapping by DB column.
*/
public Map<String, String> getMapping() {
return propertyMap;
}
/**
* Return the index position by bean property name.
*/
public int getIndexPosition(String property) {
Column c = propertyColumnMap.get(property);
return c == null ? -1 : c.getIndexPos();
}
/**
* Return an iterator of the Columns.
*/
public Iterator<Column> getColumns() {
return dbColumnMap.values().iterator();
}
/**
* Modify any column mappings with the given table alias to have the path prefix.
* <p>
* For example modify all mappings with table alias "c" to have the path prefix "customer".
* </p>
* <p>
* For the "Root type" you don't need to specify a tableAliasMapping.
* </p>
*/
public void tableAliasMapping(String tableAlias, String path) {
String startMatch = tableAlias + ".";
for (Map.Entry<String, Column> entry : dbColumnMap.entrySet()) {
if (entry.getKey().startsWith(startMatch)) {
entry.getValue().tableAliasMapping(path);
}
}
}
/**
* A Column of the RawSql that is mapped to a bean property (or ignored).
*/
public static class Column implements Serializable {
private static final long serialVersionUID = 1L;
private final int indexPos;
private final String dbColumn;
private final String dbAlias;
private String propertyName;
/**
* Construct a Column.
*/
public Column(int indexPos, String dbColumn, String dbAlias) {
this(indexPos, dbColumn, dbAlias, derivePropertyName(dbAlias, dbColumn));
}
private Column(int indexPos, String dbColumn, String dbAlias, String propertyName) {
this.indexPos = indexPos;
this.dbColumn = dbColumn;
this.dbAlias = dbAlias;
if (propertyName == null && dbAlias != null) {
this.propertyName = dbAlias;
} else {
this.propertyName = propertyName;
}
}
protected static String derivePropertyName(String dbAlias, String dbColumn) {
if (dbAlias != null) {
return CamelCaseHelper.toCamelFromUnderscore(dbAlias);
}
int dotPos = dbColumn.indexOf('.');
if (dotPos > -1) {
dbColumn = dbColumn.substring(dotPos + 1);
}
return CamelCaseHelper.toCamelFromUnderscore(dbColumn);
}
private void checkMapping() {
if (propertyName == null) {
String msg = "No propertyName defined (Column mapping) for dbColumn [" + dbColumn + "]";
throw new IllegalStateException(msg);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Column that = (Column) o;
if (indexPos != that.indexPos) return false;
if (!dbColumn.equals(that.dbColumn)) return false;
if (dbAlias != null ? !dbAlias.equals(that.dbAlias) : that.dbAlias != null) return false;
return propertyName != null ? propertyName.equals(that.propertyName) : that.propertyName == null;
}
@Override
public int hashCode() {
int result = indexPos;
result = 92821 * result + dbColumn.hashCode();
result = 92821 * result + (dbAlias != null ? dbAlias.hashCode() : 0);
result = 92821 * result + (propertyName != null ? propertyName.hashCode() : 0);
return result;
}
@Override
public String toString() {
return dbColumn + "->" + propertyName;
}
/**
* Return the index position of this column.
*/
public int getIndexPos() {
return indexPos;
}
/**
* Return the DB column alias if specified otherwise DB column.
* This is used as the key for mapping a column to a logical property.
*/
public String getDbColumnKey() {
return (dbAlias != null) ? dbAlias : dbColumn;
}
/**
* Return the DB column name including table alias (if it has one).
*/
public String getDbColumn() {
return dbColumn;
}
/**
* Return the bean property this column is mapped to.
*/
public String getPropertyName() {
return propertyName;
}
/**
* Set the property name mapped to this db column.
*/
private void setPropertyName(String propertyName) {
this.propertyName = propertyName;
}
/**
* Prepend the path to the property name.
* <p/>
* For example if path is "customer" then "name" becomes "customer.name".
*/
public void tableAliasMapping(String path) {
if (path != null) {
propertyName = path + "." + propertyName;
}
}
}
}
/**
* A key for the RawSql object using for the query plan.
*/
public static final class Key {
private final boolean parsed;
private final ColumnMapping columnMapping;
private final String unParsedSql;
Key(boolean parsed, String unParsedSql, ColumnMapping columnMapping) {
this.parsed = parsed;
this.unParsedSql = unParsedSql;
this.columnMapping = columnMapping;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Key that = (Key) o;
return parsed == that.parsed
&& columnMapping.equals(that.columnMapping)
&& unParsedSql.equals(that.unParsedSql);
}
@Override
public int hashCode() {
int result = (parsed ? 1 : 0);
result = 92821 * result + columnMapping.hashCode();
result = 92821 * result + unParsedSql.hashCode();
return result;
}
}
}
+11 -55
View File
@@ -1,8 +1,5 @@
package io.ebean;
import io.ebean.RawSql.ColumnMapping;
import io.ebean.RawSql.Sql;
import java.sql.ResultSet;
/**
@@ -14,18 +11,7 @@ import java.sql.ResultSet;
*
* @see RawSql
*/
public class RawSqlBuilder {
/**
* Special property name assigned to a DB column that should be ignored.
*/
public static final String IGNORE_COLUMN = "$$_IGNORE_COLUMN_$$";
private final ResultSet resultSet;
private final Sql sql;
private final ColumnMapping columnMapping;
public interface RawSqlBuilder {
/**
* Create and return a RawSql object based on the resultSet and list of properties the columns in
@@ -34,8 +20,8 @@ public class RawSqlBuilder {
* The properties listed in the propertyNames must be in the same order as the columns in the
* resultSet.
*/
public static RawSql resultSet(ResultSet resultSet, String... propertyNames) {
return new RawSql(resultSet, propertyNames);
static RawSql resultSet(ResultSet resultSet, String... propertyNames) {
return XServiceProvider.get().resultSet(resultSet, propertyNames);
}
/**
@@ -43,10 +29,8 @@ public class RawSqlBuilder {
* modified - so no additional WHERE or HAVING expressions can be added to
* this query.
*/
public static RawSqlBuilder unparsed(String sql) {
Sql s = new Sql(sql);
return new RawSqlBuilder(s, new ColumnMapping());
static RawSqlBuilder unparsed(String sql) {
return XServiceProvider.get().unparsed(sql);
}
/**
@@ -62,19 +46,8 @@ public class RawSqlBuilder {
* correct column names are entered into the mapping.
* </p>
*/
public static RawSqlBuilder parse(String sql) {
Sql sql2 = DRawSqlParser.parse(sql);
String select = sql2.getPreFrom();
ColumnMapping mapping = DRawSqlColumnsParser.parse(select);
return new RawSqlBuilder(sql2, mapping);
}
private RawSqlBuilder(Sql sql, ColumnMapping columnMapping) {
this.sql = sql;
this.columnMapping = columnMapping;
this.resultSet = null;
static RawSqlBuilder parse(String sql) {
return XServiceProvider.get().parsed(sql);
}
/**
@@ -87,17 +60,12 @@ public class RawSqlBuilder {
* @param dbColumn the DB column that we are mapping to a bean property
* @param propertyName the bean property that we are mapping the DB column to.
*/
public RawSqlBuilder columnMapping(String dbColumn, String propertyName) {
columnMapping.columnMapping(dbColumn, propertyName);
return this;
}
RawSqlBuilder columnMapping(String dbColumn, String propertyName);
/**
* Ignore this DB column. It is not mapped to any bean property.
*/
public RawSqlBuilder columnMappingIgnore(String dbColumn) {
return columnMapping(dbColumn, IGNORE_COLUMN);
}
RawSqlBuilder columnMappingIgnore(String dbColumn);
/**
* Modify any column mappings with the given table alias to have the path prefix.
@@ -108,24 +76,12 @@ public class RawSqlBuilder {
* For the "Root type" you don't need to specify a tableAliasMapping.
* </p>
*/
public RawSqlBuilder tableAliasMapping(String tableAlias, String path) {
columnMapping.tableAliasMapping(tableAlias, path);
return this;
}
RawSqlBuilder tableAliasMapping(String tableAlias, String path);
/**
* Create the immutable RawSql object. Do this after all the column mapping
* has been defined.
*/
public RawSql create() {
return new RawSql(resultSet, sql, columnMapping.createImmutableCopy());
}
/**
* Return the internal parsed Sql object (for testing).
*/
protected Sql getSql() {
return sql;
}
RawSql create();
}
+15 -1
View File
@@ -2,6 +2,7 @@ package io.ebean;
import java.io.Serializable;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -70,7 +71,20 @@ public interface SqlQuery extends Serializable {
* PersistenceException.
* </p>
*/
SqlRow findUnique();
SqlRow findOne();
/**
* Execute the query returning an optional row.
*/
Optional<SqlRow> findOneOrEmpty();
/**
* Deprecated - please migrate to findOne().
*/
@Deprecated
default SqlRow findUnique() {
return findOne();
}
/**
* The same as bind for named parameters.
+2
View File
@@ -1,6 +1,7 @@
package io.ebean;
import io.ebean.annotation.DocStoreMode;
import io.ebean.annotation.PersistBatch;
import io.ebean.config.DocStoreConfig;
import io.ebean.config.ServerConfig;
@@ -129,6 +130,7 @@ public interface Transaction extends AutoCloseable {
/**
* Synonym for end() to support AutoClosable.
*/
@Override
void close();
/**
-47
View File
@@ -1,47 +0,0 @@
package io.ebean;
/**
* Execute a TxCallable in a Transaction scope.
* <p>
* Use this with the {@link Ebean#execute(TxCallable)} method.
* </p>
* <p>
* Note that this is basically the same as TxRunnable except that it returns an
* Object (and you specify the return type via generics).
* </p>
* <p>
* See also {@link TxRunnable}.
* </p>
* <p>
* <pre>{@code
*
* Ebean.execute(new TxCallable<String>() {
* public String call() {
* User u1 = Ebean.find(User.class, 1);
* User u2 = Ebean.find(User.class, 2);
*
* u1.setName("u1 mod");
* u2.setName("u2 mod");
*
* Ebean.save(u1);
* Ebean.save(u2);
*
* return u1.getEmail();
* }
* });
*
* }</pre>
*
* @see TxRunnable
*/
public interface TxCallable<T> {
/**
* Execute the method within a transaction scope returning the result.
* <p>
* If you do not want to return a result you should look to use TxRunnable
* instead.
* </p>
*/
T call();
}
-40
View File
@@ -1,40 +0,0 @@
package io.ebean;
/**
* Execute a TxRunnable in a Transaction scope.
* <p>
* Use this with the {@link Ebean#execute(TxRunnable)} method.
* </p>
* <p>
* See also {@link TxCallable}.
* </p>
* <p>
* <pre>{@code
*
* // this run method runs in a transaction scope
* // which by default is TxScope.REQUIRED
*
* Ebean.execute(new TxRunnable() {
* public void run() {
* User u1 = Ebean.find(User.class, 1);
* User u2 = Ebean.find(User.class, 2);
*
* u1.setName("u1 mod");
* u2.setName("u2 mod");
*
* Ebean.save(u1);
* Ebean.save(u2);
* }
* });
*
* }</pre>
*
* @see TxCallable
*/
public interface TxRunnable {
/**
* Run the method in a transaction sope.
*/
void run();
}
+30 -7
View File
@@ -1,24 +1,27 @@
package io.ebean;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.TxIsolation;
import io.ebean.annotation.TxType;
import java.util.ArrayList;
import java.util.concurrent.Callable;
/**
* Holds the definition of how a transactional method should run.
* <p>
* This information matches the features of the Transactional annotation. You
* can use it directly with TxRunnable or TxCallable via
* {@link Ebean#execute(TxScope, TxCallable)} or
* {@link Ebean#execute(TxScope, TxRunnable)}.
* can use it directly with Runnable or Callable via
* {@link Ebean#execute(TxScope, Runnable)} or
* {@link Ebean#executeCall(TxScope, Callable)}.
* </p>
* <p>
* This object is used internally with the enhancement of a method with
* Transactional annotation.
* </p>
*
* @see TxCallable
* @see TxRunnable
* @see Ebean#execute(TxScope, TxCallable)
* @see Ebean#execute(TxScope, TxRunnable)
* @see Ebean#execute(TxScope, Runnable)
* @see Ebean#executeCall(TxScope, Callable)
*/
public final class TxScope {
@@ -38,6 +41,11 @@ public final class TxScope {
boolean readOnly;
/**
* Set this to false if the JDBC batch should not be automatically be flushed when a query is executed.
*/
boolean flushOnQuery = true;
ArrayList<Class<? extends Throwable>> rollbackFor;
ArrayList<Class<? extends Throwable>> noRollbackFor;
@@ -235,6 +243,21 @@ public final class TxScope {
return this;
}
/**
* Return false if the JDBC batch buffer should not be flushed automatically when a query is executed.
*/
public boolean isFlushOnQuery() {
return flushOnQuery;
}
/**
* Set flushOnQuery to be false to stop automatically flushing the JDBC batch buffer when a query is executed.
*/
public TxScope setFlushOnQuery(boolean flushOnQuery) {
this.flushOnQuery = flushOnQuery;
return this;
}
/**
* Return the Isolation level this transaction should run with.
*/
@@ -0,0 +1,30 @@
package io.ebean;
import io.ebean.service.SpiRawSqlService;
import java.util.Iterator;
import java.util.ServiceLoader;
/**
* Lookup internal services.
*/
class XServiceProvider {
private static SpiRawSqlService builder = init();
private static SpiRawSqlService init() {
Iterator<SpiRawSqlService> loader = ServiceLoader.load(SpiRawSqlService.class).iterator();
if (loader.hasNext()) {
return loader.next();
}
throw new IllegalStateException("No service implementation found for SpiRawSqlService?");
}
/**
* Return the RawSqlService implementation.
*/
static SpiRawSqlService get() {
return builder;
}
}
@@ -1,138 +0,0 @@
package io.ebean.annotation;
import io.ebean.TxIsolation;
import io.ebean.TxType;
import io.ebean.PersistBatch;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specify transaction scoping for a method.
* <p>
* <b><i> This is only supported if "Enhancement" is used via javaagent, ANT
* task or IDE enhancement plugin etc. </i></b>
* </p>
* <p>
* Note: Currently there are 3 known annotations that perform this role.
* <ul>
* <li>EJB's javax.ejb.TransactionAttribute</li>
* <li>Spring's org.springframework.transaction.annotation.Transactional</li>
* <li>and this one, Ebean's own Transactional</li>
* </ul>
* Spring created their one because the EJB annotation does not support features
* such as isolation level and specifying rollbackOn, noRollbackOn exceptions.
* This one exists for Ebean because I agree that the standard one is
* insufficient and don't want to include a dependency on Spring.
* </p>
* <p>
* The default behaviour of EJB (and hence Spring) is to NOT ROLLBACK on checked
* exceptions. I find this very counter-intuitive. Ebean will provide a property
* to set the default behaviour to rollback on any exception and optionally
* change the setting to be consistent with EJB/Spring if people wish to do so.
* </p>
* <p>
* <pre>{@code
*
* // a normal class
* public class MySimpleUserService {
*
* // this method is transactional automatically handling
* // transaction begin, commit and rollback etc
* @Transactional
* public void runInTrans() throws IOException {
*
* // tasks performed within the transaction
* ...
* // find some objects
* Customer cust = ebeanServer.find(Customer.class, 42);
*
* Order order = ...;
* ...
* // save some objects
* ebeanServer.save(customer);
* ebeanServer.save(order);
* }
*
* }</pre>
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Transactional {
/**
* The type of transaction scoping. Defaults to REQUIRED.
*/
TxType type() default TxType.REQUIRED;
/**
* Persist batch mode for the transaction.
*/
PersistBatch batch() default PersistBatch.INHERIT;
/**
* Persist batch mode for the request if not set on the transaction.
* <p>
* If batch is set to NONE then batchOnCascade can be set to INSERT or ALL
* and then each save(), delete(), insert(), update() request that cascades
* to child beans can use JDBC batch.
* </p>
*/
PersistBatch batchOnCascade() default PersistBatch.INHERIT;
/**
* The batch size to use when using JDBC batch mode.
* <p>
* If unset this defaults to the value set in ServerConfig.
* </p>
*/
int batchSize() default 0;
/**
* Set to false when we want to skip getting generatedKeys.
* <p>
* This is typically used in the case of large batch inserts where we get a
* performance benefit from not calling getGeneratedKeys (as we are going to
* insert a lot of rows and have no need for the Id values after the insert).
* </p>
*/
boolean getGeneratedKeys() default true;
/**
* The transaction isolation level this transaction should have.
* <p>
* This will only be used if this scope creates the transaction. If the
* transaction has already started then this will currently be ignored (you
* could argue that it should throw an exception).
* </p>
*/
TxIsolation isolation() default TxIsolation.DEFAULT;
/**
* Set this to true if the transaction should be only contain queries.
*/
boolean readOnly() default false;
/**
* The name of the server that you want the transaction to be created from.
* <p>
* If left blank the 'default' server is used.
* </p>
*/
String serverName() default "";
// int timeout() default 0;
/**
* The Throwable's that will explicitly cause a rollback to occur.
*/
Class<? extends Throwable>[] rollbackFor() default {};
/**
* The Throwable's that will explicitly NOT cause a rollback to occur.
*/
Class<? extends Throwable>[] noRollbackFor() default {};
}
@@ -1,4 +0,0 @@
/**
* Transactional annotations.
*/
package io.ebean.annotation;
@@ -226,4 +226,9 @@ public interface BeanCollection<E> extends Serializable {
* additions and removals have been processed.
*/
void modifyReset();
/**
* Return a shallow copy of this collection that is modifiable.
*/
BeanCollection<E> getShallowCopy();
}
@@ -0,0 +1,22 @@
package io.ebean.bean;
/**
* Visitor for collecting new/old values for a bean update.
*/
public interface BeanDiffVisitor {
/**
* Collect a new/old value pair.
*/
void visit(int position, Object newVal, Object oldVal);
/**
* Start processing an associated bean.
*/
void visitPush(int position);
/**
* Stop processing an associated bean.
*/
void visitPop();
}
@@ -3,6 +3,7 @@ package io.ebean.bean;
import io.ebean.Ebean;
import io.ebean.ValuePair;
import java.util.Arrays;
import javax.persistence.EntityNotFoundException;
import javax.persistence.PersistenceException;
import java.beans.PropertyChangeEvent;
@@ -506,6 +507,15 @@ public final class EntityBeanIntercept implements Serializable {
loadedProps[propertyIndex] = true;
}
/**
* Set all properties to be loaded (post insert).
*/
public void setLoadedPropertyAll() {
for (int i = 0; i < loadedProps.length; i++) {
loadedProps[i] = true;
}
}
/**
* Return true if the property is loaded.
*/
@@ -693,6 +703,28 @@ public final class EntityBeanIntercept implements Serializable {
}
}
/**
* Recursively add dirty properties.
*/
public void addDirtyPropertyValues(BeanDiffVisitor visitor) {
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
if (changedProps != null && changedProps[i]) {
// the property has been changed on this bean
Object newVal = owner._ebean_getField(i);
Object oldVal = getOrigValue(i);
visitor.visit(i, newVal, oldVal);
} else if (embeddedDirty != null && embeddedDirty[i]) {
// an embedded property has been changed - recurse
EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
visitor.visitPush(i);
embeddedBean._ebean_getIntercept().addDirtyPropertyValues(visitor);
visitor.visitPop();
}
}
}
/**
* Return a dirty property hash taking into account embedded beans.
*/
@@ -1026,7 +1058,7 @@ public final class EntityBeanIntercept implements Serializable {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (oldValue != newValue) {
} else if (Double.compare(oldValue, newValue) != 0) {
setChangedPropertyValue(propertyIndex, intercept, oldValue);
} else {
return null;
@@ -1041,7 +1073,7 @@ public final class EntityBeanIntercept implements Serializable {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (oldValue != newValue) {
} else if (Float.compare(oldValue, newValue) != 0) {
setChangedPropertyValue(propertyIndex, intercept, oldValue);
} else {
return null;
@@ -1101,7 +1133,7 @@ public final class EntityBeanIntercept implements Serializable {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (!areEqualChars(oldValue, newValue)) {
} else if (!Arrays.equals(oldValue, newValue)) {
setChangedPropertyValue(propertyIndex, intercept, oldValue);
} else {
return null;
@@ -1116,7 +1148,7 @@ public final class EntityBeanIntercept implements Serializable {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (!areEqualBytes(oldValue, newValue)) {
} else if (!Arrays.equals(oldValue, newValue)) {
setChangedPropertyValue(propertyIndex, intercept, oldValue);
} else {
return null;
@@ -1124,45 +1156,10 @@ public final class EntityBeanIntercept implements Serializable {
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
}
private static boolean areEqualBytes(byte[] b1, byte[] b2) {
if (b1 == null) {
return (b2 == null);
} else if (b2 == null) {
return false;
} else if (b1 == b2) {
return true;
} else if (b1.length != b2.length) {
return false;
}
for (int i = 0; i < b1.length; i++) {
if (b1[i] != b2[i]) {
return false;
}
}
return true;
}
private static boolean areEqualChars(char[] b1, char[] b2) {
if (b1 == null) {
return (b2 == null);
} else if (b2 == null) {
return false;
} else if (b1 == b2) {
return true;
} else if (b1.length != b2.length) {
return false;
}
for (int i = 0; i < b1.length; i++) {
if (b1[i] != b2[i]) {
return false;
}
}
return true;
/**
* Explicitly set an old value.
*/
public void setOldValue(int propertyIndex,Object oldValue) {
setChangedPropertyValue(propertyIndex, true, oldValue);
}
}
+3 -1
View File
@@ -1,5 +1,7 @@
package io.ebean.cache;
import io.ebean.config.CurrentTenantProvider;
/**
* Defines method for constructing caches for beans and queries.
*/
@@ -8,6 +10,6 @@ public interface ServerCacheFactory {
/**
* Create the cache for the given type with options.
*/
ServerCache createCache(ServerCacheType type, String cacheKey, ServerCacheOptions cacheOptions);
ServerCache createCache(ServerCacheType type, String cacheKey, CurrentTenantProvider tenantProvider, ServerCacheOptions cacheOptions);
}
+77
View File
@@ -0,0 +1,77 @@
package io.ebean.cache;
import io.ebean.config.CurrentTenantProvider;
import java.io.Serializable;
import java.util.Objects;
/**
* Tenant aware handling for caching.
*/
public class TenantAwareKey {
private final CurrentTenantProvider tenantProvider;
/**
* Construct with a tenant provider than can be null.
*/
public TenantAwareKey(CurrentTenantProvider tenantProvider) {
this.tenantProvider = tenantProvider;
}
/**
* Return a tenant aware key.
*/
public Object key(Object key) {
if (tenantProvider != null) {
return new CacheKey(key, tenantProvider.currentId());
} else {
return key;
}
}
/**
* We use a combined key, if this serverCache is per tenant.
*/
public static final class CacheKey implements Serializable {
private static final long serialVersionUID = 1L;
final Object key;
final Object tenantId;
/**
* Create with optional tenantId.
*/
public CacheKey(Object key, Object tenantId) {
this.key = key;
this.tenantId = tenantId;
}
@Override
public int hashCode() {
int result = key.hashCode();
result = 92821 * result + Objects.hashCode(tenantId);
return result;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof CacheKey) {
CacheKey that = (CacheKey) obj;
return Objects.equals(that.key, this.key)
&& Objects.equals(that.tenantId, this.tenantId);
}
return false;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(key.toString());
if (tenantId != null) {
sb.append(":").append(tenantId);
}
return sb.toString();
}
}
}
@@ -47,8 +47,7 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
protected ModifyHolder<E> modifyHolder;
protected ModifyListenMode modifyListenMode;
protected boolean modifyAddListening;
protected boolean modifyRemoveListening;
protected boolean modifyListening;
/**
@@ -148,9 +147,7 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
public void setModifyListening(ModifyListenMode mode) {
this.modifyListenMode = mode;
this.modifyAddListening = ModifyListenMode.ALL.equals(mode);
this.modifyRemoveListening = modifyAddListening || ModifyListenMode.REMOVALS.equals(mode);
this.modifyListening = modifyRemoveListening || modifyAddListening;
this.modifyListening = mode != null && ModifyListenMode.NONE != mode;
if (modifyListening) {
// lose any existing modifications
modifyHolder = null;
@@ -173,14 +170,14 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
@Override
public void modifyAddition(E bean) {
if (modifyAddListening) {
if (modifyListening) {
getModifyHolder().modifyAddition(bean);
}
}
@Override
public void modifyRemoval(Object bean) {
if (modifyRemoveListening) {
if (modifyListening) {
getModifyHolder().modifyRemoval(bean);
}
}
@@ -216,4 +213,16 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
boolean holdsModifications() {
return modifyHolder != null && modifyHolder.hasModifications();
}
/**
* Copies all relevant properties for a clone. See {@link #getShallowCopy()}
* @param other
*/
protected void setFromOriginal(AbstractBeanCollection<E> other) {
this.disableLazyLoad = other.disableLazyLoad;
this.ebeanServerName = other.ebeanServerName;
this.loader = other.loader;
this.ownerBean = other.ownerBean;
this.propertyName = other.propertyName;
}
}
+16 -9
View File
@@ -232,7 +232,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
public void add(int index, E element) {
checkReadOnly();
init();
if (modifyAddListening) {
if (modifyListening) {
modifyAddition(element);
}
list.add(index, element);
@@ -247,7 +247,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
public boolean add(E o) {
checkReadOnly();
init();
if (modifyAddListening) {
if (modifyListening) {
if (list.add(o)) {
modifyAddition(o);
return true;
@@ -262,7 +262,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
public boolean addAll(Collection<? extends E> c) {
checkReadOnly();
init();
if (modifyAddListening) {
if (modifyListening) {
// all elements in c are added (no contains checking)
getModifyHolder().modifyAdditionAll(c);
}
@@ -273,7 +273,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
public boolean addAll(int index, Collection<? extends E> c) {
checkReadOnly();
init();
if (modifyAddListening) {
if (modifyListening) {
// all elements in c are added (no contains checking)
getModifyHolder().modifyAdditionAll(c);
}
@@ -286,7 +286,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
// TODO: when clear() and not initialised could be more clever
// and fetch just the Id's
initClear();
if (modifyRemoveListening) {
if (modifyListening) {
for (E aList : list) {
getModifyHolder().modifyRemoval(aList);
}
@@ -380,7 +380,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
public E remove(int index) {
checkReadOnly();
init();
if (modifyRemoveListening) {
if (modifyListening) {
E o = list.remove(index);
modifyRemoval(o);
return o;
@@ -392,7 +392,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
public boolean remove(Object o) {
checkReadOnly();
init();
if (modifyRemoveListening) {
if (modifyListening) {
boolean isRemove = list.remove(o);
if (isRemove) {
modifyRemoval(o);
@@ -406,7 +406,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
public boolean removeAll(Collection<?> beans) {
checkReadOnly();
init();
if (modifyRemoveListening) {
if (modifyListening) {
boolean changed = false;
for (Object bean : beans) {
if (list.remove(bean)) {
@@ -424,7 +424,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
public boolean retainAll(Collection<?> retainBeans) {
checkReadOnly();
init();
if (modifyRemoveListening) {
if (modifyListening) {
boolean changed = false;
Iterator<E> it = list.iterator();
while (it.hasNext()) {
@@ -541,4 +541,11 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
}
}
@Override
public BeanCollection<E> getShallowCopy() {
BeanList<E> copy = new BeanList<>(new CopyOnFirstWriteList<>(list));
copy.setFromOriginal(this);
return copy;
}
}
+8 -2
View File
@@ -211,7 +211,7 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
public void clear() {
checkReadOnly();
initClear();
if (modifyRemoveListening) {
if (modifyListening) {
// add all beans to the removal list
for (E bean : map.values()) {
modifyRemoval(bean);
@@ -313,7 +313,7 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
public E remove(Object key) {
checkReadOnly();
init();
if (modifyRemoveListening) {
if (modifyListening) {
E o = map.remove(key);
modifyRemoval(o);
return o;
@@ -340,4 +340,10 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
return map.values();
}
@Override
public BeanCollection<E> getShallowCopy() {
BeanMap<K, E> copy = new BeanMap<>(new LinkedHashMap<>(map));
copy.setFromOriginal(this);
return copy;
}
}
+13 -7
View File
@@ -210,7 +210,7 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
public boolean add(E o) {
checkReadOnly();
init();
if (modifyAddListening) {
if (modifyListening) {
if (set.add(o)) {
modifyAddition(o);
return true;
@@ -225,7 +225,7 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
public boolean addAll(Collection<? extends E> addCollection) {
checkReadOnly();
init();
if (modifyAddListening) {
if (modifyListening) {
boolean changed = false;
for (E bean : addCollection) {
if (set.add(bean)) {
@@ -243,7 +243,7 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
public void clear() {
checkReadOnly();
initClear();
if (modifyRemoveListening) {
if (modifyListening) {
for (E bean : set) {
modifyRemoval(bean);
}
@@ -285,7 +285,7 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
public boolean remove(Object o) {
checkReadOnly();
init();
if (modifyRemoveListening) {
if (modifyListening) {
if (set.remove(o)) {
modifyRemoval(o);
return true;
@@ -299,7 +299,7 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
public boolean removeAll(Collection<?> beans) {
checkReadOnly();
init();
if (modifyRemoveListening) {
if (modifyListening) {
boolean changed = false;
for (Object bean : beans) {
if (set.remove(bean)) {
@@ -316,7 +316,7 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
public boolean retainAll(Collection<?> beans) {
checkReadOnly();
init();
if (modifyRemoveListening) {
if (modifyListening) {
boolean changed = false;
Iterator<?> it = set.iterator();
while (it.hasNext()) {
@@ -377,5 +377,11 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
throw new IllegalStateException("This collection is in ReadOnly mode");
}
}
@Override
public BeanCollection<E> getShallowCopy() {
BeanSet<E> copy = new BeanSet<>(new LinkedHashSet<>(set));
copy.setFromOriginal(this);
return copy;
}
}
@@ -0,0 +1,180 @@
package io.ebean.common;
import java.io.Serializable;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
/**
* List that copies itself on first write access. Needed to keep memory footprint low and the ability
* to modify lists from cache.
*
* @author Roland Praml, FOCONIS AG
*/
public final class CopyOnFirstWriteList<E> extends AbstractList<E> implements List<E>, Serializable {
private static final long serialVersionUID = 1L;
/**
* The underlying List implementation.
*/
private List<E> list;
public CopyOnFirstWriteList(List<E> list) {
super();
this.list = list;
}
private volatile boolean copied = false;
@Override
public int size() {
return list.size();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public boolean contains(Object o) {
return list.contains(o);
}
@Override
public Object[] toArray() {
return list.toArray();
}
@Override
public <T> T[] toArray(T[] a) {
return list.toArray(a);
}
@Override
public boolean add(E e) {
checkCopyOnWrite();
return list.add(e);
}
@Override
public boolean remove(Object o) {
checkCopyOnWrite();
return list.remove(o);
}
@Override
public boolean containsAll(Collection<?> c) {
return list.containsAll(c);
}
@Override
public boolean addAll(Collection<? extends E> c) {
checkCopyOnWrite();
return list.addAll(c);
}
@Override
public boolean addAll(int index, Collection<? extends E> c) {
checkCopyOnWrite();
return list.addAll(index, c);
}
@Override
public boolean removeAll(Collection<?> c) {
checkCopyOnWrite();
return list.removeAll(c);
}
@Override
public boolean retainAll(Collection<?> c) {
checkCopyOnWrite();
return list.retainAll(c);
}
@Override
public void replaceAll(UnaryOperator<E> operator) {
checkCopyOnWrite();
list.replaceAll(operator);
}
@Override
public boolean removeIf(Predicate<? super E> filter) {
checkCopyOnWrite();
return list.removeIf(filter);
}
@Override
public void sort(Comparator<? super E> c) {
checkCopyOnWrite();
list.sort(c);
}
@Override
public void clear() {
if (!copied) {
list = new ArrayList<>();
copied = true;
}
}
@Override
public boolean equals(Object o) {
return list.equals(o);
}
@Override
public int hashCode() {
return list.hashCode();
}
@Override
public E get(int index) {
return list.get(index);
}
@Override
public E set(int index, E element) {
checkCopyOnWrite();
return list.set(index, element);
}
@Override
public void add(int index, E element) {
checkCopyOnWrite();
list.add(index, element);
}
@Override
public E remove(int index) {
checkCopyOnWrite();
return list.remove(index);
}
@Override
public int indexOf(Object o) {
return list.indexOf(o);
}
@Override
public int lastIndexOf(Object o) {
return list.lastIndexOf(o);
}
private void checkCopyOnWrite() {
if (!copied) {
synchronized (this) {
if (!copied) {
list = new ArrayList<>(list);
copied = true;
}
}
}
}
}
@@ -44,21 +44,29 @@ class ModifyHolder<E> implements Serializable {
}
}
private boolean undoDeletion(E bean) {
return (bean != null) && modifyDeletions.remove(bean);
}
void modifyAddition(E bean) {
if (bean != null) {
// If it is to delete then just remove the deletion
if (!modifyDeletions.remove(bean)) {
if (!undoDeletion(bean)) {
// Insert
modifyAdditions.add(bean);
}
}
}
private boolean undoAddition(Object bean) {
return (bean != null) && modifyAdditions.remove(bean);
}
@SuppressWarnings("unchecked")
void modifyRemoval(Object bean) {
if (bean != null) {
// If it is to be added then just remove the addition
if (!modifyAdditions.remove(bean)) {
if (!undoAddition(bean)) {
modifyDeletions.add((E) bean);
}
}
@@ -1,11 +1,13 @@
package io.ebean.config;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.util.AnnotationUtil;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Inheritance;
import javax.persistence.Table;
import java.lang.annotation.Annotation;
/**
* Provides some base implementation for NamingConventions.
@@ -14,16 +16,15 @@ import java.lang.annotation.Annotation;
*/
public abstract class AbstractNamingConvention implements NamingConvention {
private static final Pattern TABLE_REPLACE = Pattern.compile("{table}", Pattern.LITERAL);
private static final Pattern COLUMN_REPLACE = Pattern.compile("{column}", Pattern.LITERAL);
/**
* The Constant DEFAULT_SEQ_FORMAT.
*/
public static final String DEFAULT_SEQ_FORMAT = "{table}_seq";
/**
* Sequence Format that includes the Primary Key column
*/
public static final String TABLE_PKCOLUMN_SEQ_FORMAT = "{table}_{column}_seq";
/**
* The catalog.
*/
@@ -83,11 +84,11 @@ public abstract class AbstractNamingConvention implements NamingConvention {
@Override
public String getSequenceName(String tableName, String pkColumn) {
String s = sequenceFormat.replace("{table}", tableName);
String s = TABLE_REPLACE.matcher(sequenceFormat).replaceAll(Matcher.quoteReplacement(tableName));
if (pkColumn == null) {
pkColumn = "";
}
return s.replace("{column}", pkColumn);
return COLUMN_REPLACE.matcher(s).replaceAll(Matcher.quoteReplacement(pkColumn));
}
/**
@@ -185,45 +186,43 @@ public abstract class AbstractNamingConvention implements NamingConvention {
*/
@Override
public TableName getTableName(Class<?> beanClass) {
while (true) {
TableName tableName = getTableNameFromAnnotation(beanClass);
if (tableName == null) {
Class<?> supCls = beanClass.getSuperclass();
if (hasInheritance(supCls)) {
// get the table as per inherited class in case there
// is not a table annotation in the inheritance hierarchy
return getTableName(supCls);
TableName tableName = getTableNameFromAnnotation(beanClass);
if (tableName == null) {
Class<?> supCls = beanClass.getSuperclass();
if (hasInheritance(supCls)) {
// get the table as per inherited class in case there
// is not a table annotation in the inheritance hierarchy
beanClass = supCls;
continue;
}
tableName = getTableNameByConvention(beanClass);
}
tableName = getTableNameByConvention(beanClass);
// Use naming convention for catalog or schema,
// if not set in the annotation.
String catalog = tableName.getCatalog();
if (isEmpty(catalog)) {
catalog = getCatalog();
}
String schema = tableName.getSchema();
if (isEmpty(schema)) {
schema = getSchema();
}
return new TableName(catalog, schema, tableName.getName());
}
// Use naming convention for catalog or schema,
// if not set in the annotation.
String catalog = tableName.getCatalog();
if (isEmpty(catalog)) {
catalog = getCatalog();
}
String schema = tableName.getSchema();
if (isEmpty(schema)) {
schema = getSchema();
}
return new TableName(catalog, schema, tableName.getName());
}
/**
* Return true if this class is part of entity inheritance.
*/
protected boolean hasInheritance(Class<?> supCls) {
return hasAnnotation(supCls, Inheritance.class) || hasAnnotation(supCls, DiscriminatorValue.class);
return AnnotationUtil.findAnnotationRecursive(supCls, Inheritance.class) != null
|| AnnotationUtil.findAnnotation(supCls, DiscriminatorValue.class) != null;
}
/**
* Return true if the class has the given annotation.
*/
protected boolean hasAnnotation(Class<?> supCls, Class<? extends Annotation> annotation) {
return supCls.getAnnotation(annotation) != null;
}
@Override
public TableName getM2MJoinTableName(TableName lhsTable, TableName rhsTable) {
@@ -254,7 +253,7 @@ public abstract class AbstractNamingConvention implements NamingConvention {
*/
protected TableName getTableNameFromAnnotation(Class<?> beanClass) {
final Table t = findTableAnnotation(beanClass);
final Table t = AnnotationUtil.findAnnotationRecursive(beanClass, Table.class);
// Take the annotation if defined
if (t != null && !isEmpty(t.name())) {
@@ -267,20 +266,6 @@ public abstract class AbstractNamingConvention implements NamingConvention {
return null;
}
/**
* Search recursively for an @Table in the class hierarchy.
*/
protected Table findTableAnnotation(Class<?> cls) {
if (cls.equals(Object.class)) {
return null;
}
Table table = cls.getAnnotation(Table.class);
if (table != null) {
return table;
}
return findTableAnnotation(cls.getSuperclass());
}
/**
* Replace back ticks (if they are used) with database platform specific
* quoted identifiers.
@@ -118,7 +118,7 @@ public class ClassLoadConfig {
/**
* Wraps the preferred, caller and context class loaders.
*/
protected class ClassLoaderContext {
protected static class ClassLoaderContext {
/**
* Optional - if set only use this classLoader (no fallback).
@@ -1,7 +1,7 @@
package io.ebean.config;
import io.ebean.config.dbplatform.DbType;
import io.ebean.Platform;
import io.ebean.annotation.Platform;
/**
* Custom mappings for DB types that override the default.
@@ -1,12 +1,13 @@
package io.ebean.config;
import io.ebean.Platform;
import io.ebean.dbmigration.MigrationConfig;
import io.ebean.dbmigration.MigrationRunner;
import io.ebean.annotation.Platform;
import io.ebean.migration.MigrationConfig;
import io.ebean.migration.MigrationRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.Properties;
/**
* Configuration for the DB migration processing.
@@ -443,7 +444,7 @@ public class DbMigrationConfig {
/**
* Create the MigrationRunner to run migrations if necessary.
*/
public MigrationRunner createRunner(ClassLoader classLoader) {
public MigrationRunner createRunner(ClassLoader classLoader, Properties properties) {
MigrationConfig runnerConfig = new MigrationConfig();
runnerConfig.setMetaTable(metaTable);
@@ -454,6 +455,9 @@ public class DbMigrationConfig {
runnerConfig.setDbUsername(getDbUsername());
runnerConfig.setDbPassword(getDbPassword());
runnerConfig.setClassLoader(classLoader);
if (properties != null) {
runnerConfig.load(properties);
}
return new MigrationRunner(runnerConfig);
}
}
@@ -2,7 +2,7 @@ package io.ebean.config;
import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import io.ebean.Platform;
import io.ebean.annotation.Platform;
import java.util.ArrayList;
import java.util.List;
@@ -31,7 +31,7 @@ public class MatchingNamingConvention extends AbstractNamingConvention {
@Override
public String getColumnFromProperty(Class<?> beanClass, String propertyName) {
return propertyName;
return quoteIdentifiers(propertyName);
}
@Override
@@ -47,8 +47,11 @@ public class MatchingNamingConvention extends AbstractNamingConvention {
@Override
public String getForeignKey(String prefix, String fkProperty) {
prefix = databasePlatform.unQuote(prefix);
fkProperty = databasePlatform.unQuote(fkProperty);
// add fkProperty as init caps
return prefix + fkProperty.substring(0, 1).toUpperCase() + fkProperty.substring(1);
String fullName = prefix + fkProperty.substring(0, 1).toUpperCase() + fkProperty.substring(1);
return quoteIdentifiers(fullName);
}
}
@@ -91,7 +91,7 @@ final class PropertyExpression {
// 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 + "]");
logger.debug("Unable to evaluate expression [{}]", exp);
return null;
}
}
@@ -1,5 +1,7 @@
package io.ebean.config;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -18,6 +20,8 @@ final class PropertyMapLoader {
private static final Logger logger = LoggerFactory.getLogger(PropertyMapLoader.class);
private static final Pattern OTHER_PROPS_REPLACE = Pattern.compile("\\", Pattern.LITERAL);
/**
* Load the <code>test-ebean.properties</code>.
*/
@@ -109,10 +113,10 @@ final class PropertyMapLoader {
otherProps = p.remove("load.properties.override");
}
if (otherProps != null) {
otherProps = otherProps.replace("\\", "/");
otherProps = OTHER_PROPS_REPLACE.matcher(otherProps).replaceAll(Matcher.quoteReplacement("/"));
InputStream is = findInputStream(otherProps);
if (is != null) {
logger.debug("loading properties from " + otherProps);
logger.debug("loading properties from {}", otherProps);
load(p, is);
} else {
logger.error("load.properties " + otherProps + " not found.");
@@ -135,12 +139,12 @@ final class PropertyMapLoader {
File f = new File(fileName);
if (f.exists()) {
logger.debug(fileName + " found in file system");
logger.debug("{} found in file system", fileName);
return new FileInputStream(f);
} else {
InputStream in = findInClassPath(fileName);
if (in != null) {
logger.debug(fileName + " found in classpath");
logger.debug("{} found in classpath", fileName);
}
return in;
}
@@ -1,27 +1,24 @@
package io.ebean.config;
/**
* Matches the functionality of javax.persistence.AttributeConverter
* <p>
* In general AttributeConverter should be used in preference to this
* ScalarTypeConverter as it is JPA standard and offers the same functionality.
* </p>
* <p>
* For Ebean we will look to deprecate this interface in preference to AttributeConverter.
* </p>
* <p>
* Used to convert between a value object and a known scalar type. The value
* object is the logical type used in your application and the scalar type is
* the value used to persist than to the DB.
* </p>
* <p>
* The Value object should be immutable and scalar (aka not compound) and
* converts to and from a known scalar type which Ebean will use to persist the
* value.
* </p>
* <p>
* This is an easier alternative to implementing the
* io.ebean.server.type.ScalarType interface.
* </p>
* <p>
* Note that Ebean will automatically try to detect Immutable Scalar Value
* Objects and automatically support them via reflection. This however would not
* be appropriate when the logical type is different from the type you wish to
* use for persistence - for example, if the logical type was long and you
* wanted to use java.sql.Timestamp for persistence. In this case you would want
* to implement this interface rather than let Ebean automatically support that
* type via reflection.
* </p>
*
* @param <B> The value object type.
* @param <S> The scalar object type that is used to persist the value object.
+96 -48
View File
@@ -2,17 +2,16 @@ package io.ebean.config;
import com.fasterxml.jackson.core.JsonFactory;
import io.ebean.EbeanServerFactory;
import io.ebean.PersistBatch;
import io.ebean.PersistenceContextScope;
import io.ebean.Platform;
import io.ebean.Query;
import io.ebean.Transaction;
import io.ebean.annotation.Encrypted;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.Platform;
import io.ebean.cache.ServerCachePlugin;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbEncrypt;
import io.ebean.config.dbplatform.DbType;
import io.ebean.dbmigration.MigrationRunner;
import io.ebean.event.BeanFindController;
import io.ebean.event.BeanPersistController;
import io.ebean.event.BeanPersistListener;
@@ -27,10 +26,13 @@ import io.ebean.event.changelog.ChangeLogRegister;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.meta.MetaInfoManager;
import io.ebean.migration.MigrationRunner;
import io.ebean.util.StringHelper;
import org.avaje.datasource.DataSourceConfig;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -80,12 +82,6 @@ public class ServerConfig {
*/
private String name = "db";
/**
* When false (default) H2 automatically uses DDL generate and run
* (i.e. assumes we are running tests using in memory h2).
*/
private boolean h2ProductionMode;
/**
* Typically configuration type objects that are passed by this ServerConfig
* to plugins. For example - IgniteConfiguration passed to Ignite plugin.
@@ -129,6 +125,8 @@ public class ServerConfig {
private TenantSchemaProvider tenantSchemaProvider;
private TenantCatalogProvider tenantCatalogProvider;
/**
* List of interesting classes such as entities, embedded, ScalarTypes,
* Listeners, Finders, Controllers etc.
@@ -317,6 +315,8 @@ public class ServerConfig {
*/
private String databaseBooleanFalse;
private boolean allQuotedIdentifiers;
/**
* The naming convention.
*/
@@ -438,6 +438,16 @@ public class ServerConfig {
*/
private boolean disableL2Cache;
/**
* The time in millis used to determine when a query is alerted for being slow.
*/
private long slowQueryMillis;
/**
* The listener for processing slow query events.
*/
private SlowQueryListener slowQueryListener;
/**
* Construct a Server Configuration for programmatically creating an EbeanServer.
*/
@@ -445,6 +455,34 @@ public class ServerConfig {
}
/**
* Return the slow query time in millis.
*/
public long getSlowQueryMillis() {
return slowQueryMillis;
}
/**
* Set the slow query time in millis.
*/
public void setSlowQueryMillis(long slowQueryMillis) {
this.slowQueryMillis = slowQueryMillis;
}
/**
* Return the slow query event listener.
*/
public SlowQueryListener getSlowQueryListener() {
return slowQueryListener;
}
/**
* Set the slow query event listener.
*/
public void setSlowQueryListener(SlowQueryListener slowQueryListener) {
this.slowQueryListener = slowQueryListener;
}
/**
* Put a service object into configuration such that it can be passed to a plugin.
* <p>
@@ -525,27 +563,6 @@ public class ServerConfig {
this.name = name;
}
/**
* Return true if H2 should be used in production mode.
* <p>
* Otherwise it is assumed we are using H2 for testing and DDL generate and run is turned on.
* </p>
*/
public boolean isH2ProductionMode() {
return h2ProductionMode;
}
/**
* Set to true for H2 to be used in production mode.
* <p>
* Do this when we want to use H2 and not have the DDL generation and run automatically turned on.
* Otherwise it is assumed we are using H2 for testing purposes.
* </p>
*/
public void setH2ProductionMode(boolean h2ProductionMode) {
this.h2ProductionMode = h2ProductionMode;
}
/**
* Return the container / clustering configuration.
* <p/>
@@ -696,6 +713,20 @@ public class ServerConfig {
this.tenantSchemaProvider = tenantSchemaProvider;
}
/**
* Return the tenancy catalog provider.
*/
public TenantCatalogProvider getTenantCatalogProvider() {
return tenantCatalogProvider;
}
/**
* Set the tenancy catalog provider.
*/
public void setTenantCatalogProvider(TenantCatalogProvider tenantCatalogProvider) {
this.tenantCatalogProvider = tenantCatalogProvider;
}
/**
* Return the PersistBatch mode to use by default at the transaction level.
* <p>
@@ -1285,6 +1316,24 @@ public class ServerConfig {
this.namingConvention = namingConvention;
}
/**
* Return true if all DB column and table names should use quoted identifiers.
*/
public boolean isAllQuotedIdentifiers() {
return allQuotedIdentifiers;
}
/**
* Set to true if all DB column and table names should use quoted identifiers.
*/
public void setAllQuotedIdentifiers(boolean allQuotedIdentifiers) {
this.allQuotedIdentifiers = allQuotedIdentifiers;
if (allQuotedIdentifiers && namingConvention instanceof UnderscoreNamingConvention) {
// we need to use matching naming convention
this.namingConvention = new MatchingNamingConvention();
}
}
/**
* Return true if this EbeanServer is a Document store only instance (has no JDBC DB).
*/
@@ -1551,11 +1600,6 @@ public class ServerConfig {
*/
public void setDatabasePlatform(DatabasePlatform databasePlatform) {
this.databasePlatform = databasePlatform;
if (!h2ProductionMode && databasePlatform != null && databasePlatform.isPlatform(Platform.H2)) {
// we are using H2 to run tests so turn on DDL generation and run
this.ddlGenerate = true;
this.ddlRun = true;
}
}
/**
@@ -2488,6 +2532,11 @@ public class ServerConfig {
migrationConfig.loadSettings(p, name);
boolean quotedIdentifiers = p.getBoolean("allQuotedIdentifiers", allQuotedIdentifiers);
if (quotedIdentifiers != allQuotedIdentifiers) {
// potentially also set to use matching naming convention
setAllQuotedIdentifiers(quotedIdentifiers);
}
namingConvention = createNamingConvention(p, namingConvention);
if (namingConvention != null) {
namingConvention.loadFromProperties(p);
@@ -2512,6 +2561,7 @@ public class ServerConfig {
dbTypeConfig.setGeometrySRID(srid);
}
slowQueryMillis = p.getLong("slowQueryMillis", slowQueryMillis);
docStoreOnly = p.getBoolean("docStoreOnly", docStoreOnly);
disableL2Cache = p.getBoolean("disableL2Cache", disableL2Cache);
explicitTransactionBeginMode = p.getBoolean("explicitTransactionBeginMode", explicitTransactionBeginMode);
@@ -2594,7 +2644,6 @@ public class ServerConfig {
jsonDateTime = JsonConfig.DateTime.MILLIS;
}
h2ProductionMode = p.getBoolean("h2ProductionMode", h2ProductionMode);
ddlGenerate = p.getBoolean("ddl.generate", ddlGenerate);
ddlRun = p.getBoolean("ddl.run", ddlRun);
ddlCreateOnly = p.getBoolean("ddl.createOnly", ddlCreateOnly);
@@ -2625,10 +2674,9 @@ public class ServerConfig {
List<Class<?>> classes = new ArrayList<>();
String[] split = classNames.split("[ ,;]");
for (String aSplit : split) {
String cn = aSplit.trim();
if (!cn.isEmpty() && !"class".equalsIgnoreCase(cn)) {
String[] split = StringHelper.splitNames(classNames);
for (String cn : split) {
if (!"class".equalsIgnoreCase(cn)) {
try {
classes.add(Class.forName(cn));
} catch (ClassNotFoundException e) {
@@ -2642,16 +2690,16 @@ public class ServerConfig {
private List<String> getSearchJarsPackages(String searchPackages) {
List<String> hitList = new ArrayList<>();
if (searchPackages != null) {
String[] entries = StringHelper.splitNames(searchPackages);
String[] entries = searchPackages.split("[ ,;]");
for (String entry : entries) {
hitList.add(entry.trim());
}
List<String> hitList = new ArrayList<>(entries.length);
Collections.addAll(hitList, entries);
return hitList;
} else {
return new ArrayList<>();
}
return hitList;
}
/**
@@ -2739,7 +2787,7 @@ public class ServerConfig {
*/
public DataSource runDbMigration(DataSource dataSource) {
if (migrationConfig.isRunMigration()) {
MigrationRunner runner = migrationConfig.createRunner(getClassLoadConfig().getClassLoader());
MigrationRunner runner = migrationConfig.createRunner(getClassLoadConfig().getClassLoader(), properties);
runner.run(dataSource);
}
return dataSource;
@@ -0,0 +1,59 @@
package io.ebean.config;
import io.ebean.bean.ObjectGraphNode;
/**
* Slow query event.
*/
public class SlowQueryEvent {
private final String sql;
private final long timeMillis;
private final int rowCount;
private final ObjectGraphNode originNode;
/**
* Construct with the SQL and execution time in millis.
*/
public SlowQueryEvent(String sql, long timeMillis, int rowCount, ObjectGraphNode originNode) {
this.sql = sql;
this.timeMillis = timeMillis;
this.rowCount = rowCount;
this.originNode = originNode;
}
/**
* Return the SQL for the slow query.
*/
public String getSql() {
return sql;
}
/**
* Return the execution time in millis.
*/
public long getTimeMillis() {
return timeMillis;
}
/**
* Return the total row count associated with the query.
*/
public int getRowCount() {
return rowCount;
}
/**
* Return the origin point for the root query.
* <p>
* Typically the <code>originNode.getOriginQueryPoint().getFirstStackElement()</code> provides the stack line that
* shows the code that invoked the query.
* </p>
*/
public ObjectGraphNode getOriginNode() {
return originNode;
}
}
@@ -0,0 +1,13 @@
package io.ebean.config;
/**
* Listener for slow query events.
*/
@FunctionalInterface
public interface SlowQueryListener {
/**
* Process a slow query event.
*/
void process(SlowQueryEvent event);
}
@@ -0,0 +1,16 @@
package io.ebean.config;
/**
* For multi-tenancy via DB CATALOG supply the catalog given the tenantId.
*/
@FunctionalInterface
public interface TenantCatalogProvider {
/**
* Return the DB catalog for the given tenantId.
*
* @param tenantId The current tenant id.
* @return The DB catalog to use for the given tenant
*/
String catalog(Object tenantId);
}
@@ -20,6 +20,11 @@ public enum TenantMode {
*/
SCHEMA(true),
/**
* Each Tenant has their own Database but with in connection pool
*/
CATALOG(true),
/**
* Tenants share tables but have a discriminator/partition column that partitions the data.
*/
@@ -2,13 +2,10 @@ package io.ebean.config.dbplatform;
import io.ebean.BackgroundExecutor;
import io.ebean.Query;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.Platform;
import io.ebean.config.CustomDbTypeMapping;
import io.ebean.config.DbTypeConfig;
import io.ebean.PersistBatch;
import io.ebean.Platform;
import io.ebean.config.ServerConfig;
import io.ebean.dbmigration.ddlgeneration.DdlHandler;
import io.ebean.dbmigration.ddlgeneration.platform.PlatformDdl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -64,6 +61,11 @@ public class DatabasePlatform {
*/
protected String closeQuote = "\"";
/**
* When set to true all db column names and table names use quoted identifiers.
*/
protected boolean allQuotedIdentifiers;
/**
* For limit/offset, row_number etc limiting of SQL queries.
*/
@@ -157,8 +159,6 @@ public class DatabasePlatform {
*/
protected PersistBatch persistBatchOnCascade = PersistBatch.ALL;
protected PlatformDdl platformDdl;
/**
* The maximum length of table names - used specifically when derived
* default table names for intersection tables.
@@ -175,6 +175,8 @@ public class DatabasePlatform {
protected SqlExceptionTranslator exceptionTranslator = new SqlCodeTranslator();
protected char[] specialLikeCharacters = { '%', '_', '\\' };
/**
* Instantiates a new database platform.
*/
@@ -191,7 +193,8 @@ public class DatabasePlatform {
/**
* Configure UUID Storage etc based on ServerConfig settings.
*/
public void configure(DbTypeConfig config) {
public void configure(DbTypeConfig config, boolean allQuotedIdentifiers) {
this.allQuotedIdentifiers = allQuotedIdentifiers;
addGeoTypes(config.getGeometrySRID());
configureIdType(config.getIdType());
dbTypeMap.config(nativeUuidType, config.getDbUuid());
@@ -268,20 +271,6 @@ public class DatabasePlatform {
return maxConstraintNameLength;
}
/**
* Return the platform specific DDL.
*/
public PlatformDdl getPlatformDdl() {
return platformDdl;
}
/**
* Create and return a DDL handler for generating DDL scripts.
*/
public DdlHandler createDdlHandler(ServerConfig serverConfig) {
return platformDdl.createDdlHandler(serverConfig);
}
/**
* Return true if the JDBC driver does not allow additional queries to execute
* when a resultSet is being 'streamed' as is the case with findEach() etc.
@@ -533,24 +522,33 @@ public class DatabasePlatform {
* naming rules.
* </p>
*
* @param dbName the db name
* @return the string
* @param dbName the db table or column name
* @return the db table or column name with potentially platform specific quoted identifiers
*/
public String convertQuotedIdentifiers(String dbName) {
// Ignore null values e.g. schema name or catalog
if (dbName != null && !dbName.isEmpty()) {
if (dbName.charAt(0) == BACK_TICK) {
if (dbName.charAt(dbName.length() - 1) == BACK_TICK) {
String quotedName = getOpenQuote();
quotedName += dbName.substring(1, dbName.length() - 1);
quotedName += getCloseQuote();
return quotedName;
return openQuote + dbName.substring(1, dbName.length() - 1) + closeQuote;
} else {
logger.error("Missing backquote on [" + dbName + "]");
}
} else if (allQuotedIdentifiers) {
return openQuote + dbName + closeQuote;
}
}
return dbName;
}
/**
* Remove quoted identifier quotes from the table or column name if present.
*/
public String unQuote(String dbName) {
if (dbName != null && !dbName.isEmpty()) {
if (dbName.startsWith(openQuote)) {
// trim off the open and close quotes
return dbName.substring(1, dbName.length()-1);
}
}
return dbName;
@@ -617,4 +615,37 @@ public class DatabasePlatform {
logger.error("Error closing resultSet", e);
}
}
/**
* Escapes the like string for this DB-Platform
*/
public String escapeLikeString(String value) {
StringBuilder sb = null;
for (int i = 0; i < value.length(); i++) {
char ch = value.charAt(i);
boolean escaped = false;
for (char escapeChar: specialLikeCharacters) {
if (ch == escapeChar) {
if (sb == null) {
sb = new StringBuilder(value.substring(0, i));
}
escapeLikeCharacter(escapeChar, sb);
escaped = true;
break;
}
}
if (!escaped && sb != null) {
sb.append(ch);
}
}
if (sb == null) {
return value;
} else {
return sb.toString();
}
}
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
sb.append('\\').append(ch);
}
}
@@ -1,8 +1,13 @@
package io.ebean.config.dbplatform;
import java.sql.Types;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.xml.bind.DatatypeConverter;
import io.ebean.annotation.DbDefault;
/**
* DB Column default values mapping to database platform specific literals.
*/
@@ -22,6 +27,13 @@ public class DbDefaultValue {
* The key for the NOW / current timestamp.
*/
public static final String NOW = "now";
/**
* The 'null' literal.
*/
public static final String NULL = "null";
protected Map<String, String> map = new LinkedHashMap<>();
@@ -63,8 +75,128 @@ public class DbDefaultValue {
if (dbDefaultLiteral == null) {
return null;
}
if (dbDefaultLiteral.startsWith("$RAW:")) {
return dbDefaultLiteral.substring(5);
}
String val = map.get(dbDefaultLiteral);
return val != null ? val : dbDefaultLiteral;
}
/**
* This method checks & convert the {@link DbDefault#value()} to a valid SQL literal.
*
* This is mainly to quote string literals and verify integer/dates for correctness.
* <p>
* Note: There are some special cases:
* </p>
* <ul>
* <li>Normal Quoting: <code>@DbDefault("User's default")</code> on a String propery
* returns: <code>default 'User''s default'</code><br/>
* (the same on an integer property will throw a NumberFormatException)</li>
* <li>Special case null: <code>@DbDefault("null")</code> will return this: <code>default null</code><br/>
* If you need really the String "null", you have to specify <code>@DbDefault("'null'")</code>
* which gives you the <code>default 'null'</code> statement.</li>
* <li>Any statement, that begins and ends with single quote will not be checked or get quoted again.</li>
* <li>A statement that begins with "$RAW:", e.g <code>@DbDefault("$RAW:N'SANDNES'")</code> will lead to
* a <code>default N'SANDNES'</code> in DDL. Note that this is platform specific!</li>
* </ul>
*/
public static String toSqlLiteral(String defaultValue, Class<?> propertyType, int sqlType) {
if (propertyType == null
|| defaultValue == null
|| NULL.equals(defaultValue)
|| (defaultValue.startsWith("'") && defaultValue.endsWith("'"))
|| (defaultValue.startsWith("$RAW:"))) {
return defaultValue;
}
if (Boolean.class.isAssignableFrom(propertyType) || Boolean.TYPE.isAssignableFrom(propertyType)) {
return toBooleanLiteral(defaultValue);
}
if (Number.class.isAssignableFrom(propertyType)
|| Byte.TYPE.equals(propertyType)
|| Short.TYPE.equals(propertyType)
|| Integer.TYPE.equals(propertyType)
|| Long.TYPE.equals(propertyType)
|| Float.TYPE.equals(propertyType)
|| Double.TYPE.equals(propertyType)
|| (propertyType.isEnum() && sqlType == Types.INTEGER)) {
Double.valueOf(defaultValue); // verify if it is a number
return defaultValue;
}
// check if it is a date/time - in all other cases return quoted defaultValue
switch (sqlType) {
// date
case Types.DATE:
return toDateLiteral(defaultValue);
// time
case Types.TIME:
case Types.TIME_WITH_TIMEZONE:
return toTimeLiteral(defaultValue);
// timestamp
case Types.TIMESTAMP:
case Types.TIMESTAMP_WITH_TIMEZONE:
return toDateTimeLiteral(defaultValue);
default:
return toTextLiteral(defaultValue); // do not check other datatypes
}
}
/**
* Checks if specified value is either 'true' or 'false'. The literal is translated later.
*/
private static String toBooleanLiteral(String value) {
if (DbDefaultValue.FALSE.equals(value) || DbDefaultValue.TRUE.equals(value)) {
return value;
}
throw new IllegalArgumentException("'" + value + "' is not a valid value for boolean");
}
/**
* This adds single qoutes around the <code>value</code> and doubles single quotes.
* "User's home" will return "'User''s home'"
*/
private static String toTextLiteral(String value) {
StringBuilder sb = new StringBuilder(value.length()+10);
sb.append('\'');
for (int i = 0; i < value.length(); i++) {
char ch = value.charAt(i);
if (ch == '\'') {
sb.append("''");
} else {
sb.append(ch);
}
}
sb.append('\'');
return sb.toString();
}
private static String toDateLiteral(String value) {
if (NOW.equals(value)) {
return value; // this will get translated later
}
DatatypeConverter.parseDate(value); // verify
return toTextLiteral(value);
}
private static String toTimeLiteral(String value) {
if (NOW.equals(value)) {
return value; // this will get translated later
}
DatatypeConverter.parseTime(value); // verify
return toTextLiteral(value);
}
private static String toDateTimeLiteral(String value) {
if (NOW.equals(value)) {
return value; // this will get translated later
}
DatatypeConverter.parseDateTime(value); // verify
return toTextLiteral(value);
}
}
@@ -1,12 +1,15 @@
package io.ebean.config.dbplatform;
import io.ebean.dbmigration.migration.IdentityType;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Defines the identity/sequence behaviour for the database.
*/
public class DbIdentity {
private static final Pattern TABLE_REPLACE = Pattern.compile("{table}", Pattern.LITERAL);
/**
* Set if this DB supports sequences. Note some DB's support both Sequences
* and Identity.
@@ -50,7 +53,8 @@ public class DbIdentity {
if (selectLastInsertedIdTemplate == null) {
return null;
}
return selectLastInsertedIdTemplate.replace("{table}", table);
return TABLE_REPLACE.matcher(selectLastInsertedIdTemplate).replaceAll(Matcher.quoteReplacement(table));
}
/**
@@ -116,28 +120,4 @@ public class DbIdentity {
this.idType = idType;
}
/**
* Determine the id type to use based on requested identityType and
* the support for that in the database platform.
*/
public IdType useIdentityType(IdentityType identityType) {
if (identityType == null) {
// use the default
return idType;
}
switch (identityType) {
case GENERATOR:
return IdType.GENERATOR;
case EXTERNAL:
return IdType.EXTERNAL;
case SEQUENCE:
return supportsSequence ? IdType.SEQUENCE : idType;
case IDENTITY:
return supportsIdentity ? IdType.IDENTITY : idType;
default:
return idType;
}
}
}
@@ -130,7 +130,9 @@ public class DbPlatformType implements ExtraDbTypes {
if (canHaveLength || !strict) {
// see if there is a precision/scale to add (or not)
int len = deployLength != 0 ? deployLength : defaultLength;
if (len > 0) {
if (len == Integer.MAX_VALUE) {
sb.append("(max)"); // TODO: this is sqlserver specific
} else if (len > 0) {
sb.append("(");
sb.append(len);
int scale = deployScale != 0 ? deployScale : defaultScale;
@@ -30,7 +30,8 @@ class DbPlatformTypeParser {
} else {
String type = columnDefinition.substring(0, openPos);
int scale = Integer.parseInt(columnDefinition.substring(openPos + 1, closePos));
String strScale = columnDefinition.substring(openPos + 1, closePos);
int scale = strScale.equalsIgnoreCase("max") ? Integer.MAX_VALUE : Integer.parseInt(strScale);
return new DbPlatformType(type, scale);
}
} catch (RuntimeException e) {
@@ -162,9 +162,7 @@ public abstract class SequenceIdGenerator implements PlatformIdGenerator {
}
synchronized (monitor) {
for (Long newId : newIds) {
idList.add(newId);
}
idList.addAll(newIds);
}
}
@@ -1,13 +1,13 @@
package io.ebean.config.dbplatform.db2;
import io.ebean.BackgroundExecutor;
import io.ebean.Platform;
import io.ebean.annotation.PersistBatch;
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.PlatformIdGenerator;
import io.ebean.config.dbplatform.SqlErrorCodes;
import io.ebean.dbmigration.ddlgeneration.platform.DB2Ddl;
import javax.sql.DataSource;
import java.sql.Types;
@@ -23,24 +23,36 @@ public class DB2Platform extends DatabasePlatform {
this.maxTableNameLength = 18;
this.maxConstraintNameLength = 18;
this.sqlLimiter = new Db2SqlLimiter();
this.platformDdl = new DB2Ddl(this);
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsSequence(true);
this.likeClause = "like ? escape '|'";
this.specialLikeCharacters = new char[] { '%', '_', '|' };
this.exceptionTranslator =
new SqlErrorCodes()
//.addAcquireLock("")
.addDuplicateKey("-803")
.addDataIntegrity("-407","-530","-531","-532","-543","-544","-545","-603","-667")
.addAcquireLock("40001","57033") // key -911/-913
.addDuplicateKey("23505") // -803
// .addDataIntegrity("-407","-530","-531","-532","-543","-544","-545","-603","-667")
// we need SQLState, not code: https://www.ibm.com/support/knowledgecenter/en/SSEPEK_10.0.0/codes/src/tpc/db2z_n.html
.addDataIntegrity("23502","23503","23504","23511","23512","23511","42917","23515")
.build();
booleanDbType = Types.BOOLEAN;
dbTypeMap.put(DbType.TINYINT, new DbPlatformType("smallint", false));
dbTypeMap.put(DbType.INTEGER, new DbPlatformType("integer", false));
dbTypeMap.put(DbType.BIGINT, new DbPlatformType("bigint", false));
dbTypeMap.put(DbType.REAL, new DbPlatformType("real"));
dbTypeMap.put(DbType.TINYINT, new DbPlatformType("smallint"));
dbTypeMap.put(DbType.DECIMAL, new DbPlatformType("decimal", 15));
persistBatchOnCascade = PersistBatch.NONE;
}
@Override
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
sb.append('|').append(ch);
}
/**
* Return a DB2 specific sequence IdGenerator that supports batch fetching
* sequence values.
@@ -49,7 +49,7 @@ public class H2HistoryTrigger implements Trigger {
// build the insert into history table SQL
StringBuilder insertSql = new StringBuilder(150);
insertSql.append("insert into ").append(tableName).append(HISTORY_SUFFIX).append(" (");
insertSql.append("insert into ").append(schemaName).append(".").append(tableName).append(HISTORY_SUFFIX).append(" (");
int count = 0;
while (rs.next()) {
@@ -1,15 +1,14 @@
package io.ebean.config.dbplatform.h2;
import io.ebean.BackgroundExecutor;
import io.ebean.Platform;
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 io.ebean.dbmigration.ddlgeneration.platform.H2Ddl;
import javax.sql.DataSource;
@@ -22,7 +21,6 @@ public class H2Platform extends DatabasePlatform {
super();
this.platform = Platform.H2;
this.dbEncrypt = new H2DbEncrypt();
this.platformDdl = new H2Ddl(this);
this.historySupport = new H2HistorySupport();
this.nativeUuidType = true;
this.dbDefaultValue.setNow("now()");
@@ -30,7 +28,7 @@ public class H2Platform extends DatabasePlatform {
this.exceptionTranslator =
new SqlErrorCodes()
.addAcquireLock("50200")
.addAcquireLock("50200","HYT00")
.addDuplicateKey("23001","23505")
.addDataIntegrity("22001","22003","22012","22018","22025","23000","23002","23003","23502","23503","23506","23507","23513")
.build();
@@ -40,10 +38,6 @@ public class H2Platform extends DatabasePlatform {
this.dbIdentity.setSupportsSequence(true);
this.dbIdentity.setSupportsIdentity(true);
// like ? escape'' not working in the latest version H2 so just using no
// escape clause for now noting that backslash is an escape char for like in H2
this.likeClause = "like ?";
dbTypeMap.put(DbType.UUID, new DbPlatformType("uuid", false));
}
@@ -1,7 +1,7 @@
package io.ebean.config.dbplatform.hsqldb;
import io.ebean.BackgroundExecutor;
import io.ebean.Platform;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
@@ -9,7 +9,6 @@ import io.ebean.config.dbplatform.IdType;
import io.ebean.config.dbplatform.PlatformIdGenerator;
import io.ebean.config.dbplatform.h2.H2DbEncrypt;
import io.ebean.config.dbplatform.h2.H2SequenceIdGenerator;
import io.ebean.dbmigration.ddlgeneration.platform.HsqldbDdl;
import javax.sql.DataSource;
@@ -22,7 +21,6 @@ public class HsqldbPlatform extends DatabasePlatform {
super();
this.platform = Platform.HSQLDB;
this.dbEncrypt = new H2DbEncrypt();
this.platformDdl = new HsqldbDdl(this);
this.dbIdentity.setIdType(IdType.IDENTITY);
this.dbIdentity.setSupportsGetGeneratedKeys(true);
@@ -1,15 +1,14 @@
package io.ebean.config.dbplatform.mysql;
import io.ebean.BackgroundExecutor;
import io.ebean.Platform;
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 io.ebean.dbmigration.ddlgeneration.platform.MySqlDdl;
import javax.sql.DataSource;
import java.sql.Types;
@@ -30,10 +29,8 @@ public class MySqlPlatform extends DatabasePlatform {
super();
this.platform = Platform.MYSQL;
this.useExtraTransactionOnIterateSecondaryQueries = true;
this.likeClause = "like ? escape''";
this.selectCountWithAlias = true;
this.dbEncrypt = new MySqlDbEncrypt();
this.platformDdl = new MySqlDdl(this);
this.historySupport = new MySqlHistorySupport();
this.columnAliasPrefix = null;
@@ -46,11 +43,14 @@ public class MySqlPlatform extends DatabasePlatform {
new SqlErrorCodes()
.addAcquireLock("1205")
.addDuplicateKey("1062")
.addDataIntegrity("630","839","840","893","1169","1215","1216","1217","1364","1451","1452","1557")
.addDataIntegrity("630","839","840","893","1169","1215","1216","1217","1364","1451","1452","1557","23000")
.build();
this.openQuote = "`";
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.forwardOnlyHintOnFindIterate = true;
this.booleanDbType = Types.BIT;
@@ -79,4 +79,9 @@ public class MySqlPlatform extends DatabasePlatform {
// 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);
}
}
@@ -1,8 +1,8 @@
package io.ebean.config.dbplatform.oracle;
import io.ebean.BackgroundExecutor;
import io.ebean.Platform;
import io.ebean.Query;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.BasicSqlAnsiLimiter;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
@@ -10,7 +10,6 @@ import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import io.ebean.config.dbplatform.PlatformIdGenerator;
import io.ebean.config.dbplatform.RownumSqlLimiter;
import io.ebean.dbmigration.ddlgeneration.platform.Oracle10Ddl;
import javax.sql.DataSource;
import java.sql.Types;
@@ -28,7 +27,6 @@ public class OraclePlatform extends DatabasePlatform {
this.dbEncrypt = new OracleDbEncrypt();
this.sqlLimiter = new RownumSqlLimiter();
this.basicSqlLimiter = new BasicSqlAnsiLimiter();
this.platformDdl = new Oracle10Ddl(this);
this.historySupport = new OracleDbHistorySupport();
// Not using getGeneratedKeys as instead we will
@@ -39,6 +37,9 @@ public class OraclePlatform extends DatabasePlatform {
this.treatEmptyStringsAsNull = true;
this.likeClause = "like ? escape '|'";
this.specialLikeCharacters = new char[] { '%', '_', '|' };
this.openQuote = "\"";
this.closeQuote = "\"";
@@ -80,4 +81,9 @@ public class OraclePlatform extends DatabasePlatform {
return sql + " for update";
}
}
@Override
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
sb.append('|').append(ch);
}
}
@@ -1,17 +1,14 @@
package io.ebean.config.dbplatform.postgres;
import io.ebean.BackgroundExecutor;
import io.ebean.Platform;
import io.ebean.Query;
import io.ebean.config.ServerConfig;
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 io.ebean.dbmigration.ddlgeneration.DdlHandler;
import io.ebean.dbmigration.ddlgeneration.platform.PostgresDdl;
import javax.sql.DataSource;
import java.sql.Types;
@@ -28,7 +25,6 @@ public class PostgresPlatform extends DatabasePlatform {
super();
this.platform = Platform.POSTGRES;
this.supportsNativeIlike = true;
this.likeClause = "like ? escape''";
this.selectCountWithAlias = true;
this.blobDbType = Types.LONGVARBINARY;
this.clobDbType = Types.VARCHAR;
@@ -37,7 +33,6 @@ public class PostgresPlatform extends DatabasePlatform {
this.dbEncrypt = new PostgresDbEncrypt();
this.historySupport = new PostgresHistorySupport();
this.platformDdl = new PostgresDdl(this);
// Use Identity and getGeneratedKeys
this.dbIdentity.setIdType(IdType.IDENTITY);
@@ -91,14 +86,6 @@ public class PostgresPlatform extends DatabasePlatform {
return new DbPlatformType("geometry(" + type + "," + srid + ")");
}
/**
* Return a DdlHandler instance for generating DDL for the specific platform.
*/
@Override
public DdlHandler createDdlHandler(ServerConfig serverConfig) {
return this.platformDdl.createDdlHandler(serverConfig);
}
/**
* Create a Postgres specific sequence IdGenerator.
*/
@@ -1,6 +1,6 @@
package io.ebean.config.dbplatform.sqlanywhere;
import io.ebean.Platform;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
@@ -1,11 +1,10 @@
package io.ebean.config.dbplatform.sqlite;
import io.ebean.Platform;
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.dbmigration.ddlgeneration.platform.SQLiteDdl;
import java.sql.Types;
@@ -14,8 +13,6 @@ public class SQLitePlatform extends DatabasePlatform {
public SQLitePlatform() {
super();
this.platform = Platform.SQLITE;
this.platformDdl = new SQLiteDdl(this);
this.dbIdentity.setIdType(IdType.IDENTITY);
this.dbIdentity.setSupportsGetGeneratedKeys(false);
this.dbIdentity.setSupportsSequence(false);
@@ -1,13 +1,12 @@
package io.ebean.config.dbplatform.sqlserver;
import io.ebean.PersistBatch;
import io.ebean.Platform;
import io.ebean.annotation.PersistBatch;
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.SqlErrorCodes;
import io.ebean.dbmigration.ddlgeneration.platform.SqlServerDdl;
import java.sql.Types;
@@ -26,7 +25,6 @@ public class SqlServerPlatform extends DatabasePlatform {
this.selectCountWithAlias = true;
this.sqlLimiter = new SqlServerSqlLimiter();
this.basicSqlLimiter = new SqlServerBasicSqlLimiter();
this.platformDdl = new SqlServerDdl(this);
this.historySupport = new SqlServerHistorySupport();
this.dbIdentity.setIdType(IdType.IDENTITY);
this.dbIdentity.setSupportsGetGeneratedKeys(true);
@@ -35,12 +33,14 @@ public class SqlServerPlatform extends DatabasePlatform {
this.exceptionTranslator =
new SqlErrorCodes()
.addAcquireLock("1222")
.addDuplicateKey("2601","2627")
.addDataIntegrity("544","8114","8115")
.addDuplicateKey("2601", "2627")
.addDataIntegrity("544", "8114", "8115")
.build();
this.openQuote = "[";
this.closeQuote = "]";
this.specialLikeCharacters = new char[]{'%', '_', '['};
this.likeClause = "like ? COLLATE Latin1_General_BIN";
booleanDbType = Types.INTEGER;
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("bit default 0"));
@@ -61,6 +61,13 @@ public class SqlServerPlatform extends DatabasePlatform {
dbTypeMap.put(DbType.TIME, new DbPlatformType("time"));
dbTypeMap.put(DbType.TIMESTAMP, new DbPlatformType("datetime2"));
dbTypeMap.put(DbType.JSON, new DbPlatformType("nvarchar", Integer.MAX_VALUE));
dbTypeMap.put(DbType.JSONB, new DbPlatformType("nvarchar", Integer.MAX_VALUE));
}
@Override
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
sb.append('[').append(ch).append(']');
}
}
@@ -1,103 +1,52 @@
package io.ebean.dbmigration;
import io.ebean.Ebean;
import io.ebean.EbeanServer;
import io.ebean.config.DbConstraintNaming;
import io.ebean.config.DbMigrationConfig;
import io.ebean.Platform;
import io.ebean.annotation.Platform;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.db2.DB2Platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.h2.H2Platform;
import io.ebean.config.dbplatform.sqlserver.SqlServerPlatform;
import io.ebean.config.dbplatform.mysql.MySqlPlatform;
import io.ebean.config.dbplatform.oracle.OraclePlatform;
import io.ebean.config.dbplatform.postgres.PostgresPlatform;
import io.ebean.config.dbplatform.sqlite.SQLitePlatform;
import io.ebean.dbmigration.ddlgeneration.DdlWrite;
import io.ebean.dbmigration.migration.Migration;
import io.ebean.dbmigration.migrationreader.MigrationXmlWriter;
import io.ebean.dbmigration.model.CurrentModel;
import io.ebean.dbmigration.model.MConfiguration;
import io.ebean.dbmigration.model.MigrationModel;
import io.ebean.dbmigration.model.MigrationVersion;
import io.ebean.dbmigration.model.ModelContainer;
import io.ebean.dbmigration.model.ModelDiff;
import io.ebean.dbmigration.model.PlatformDdlWriter;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.extraddl.model.DdlScript;
import io.ebeaninternal.extraddl.model.ExtraDdl;
import io.ebeaninternal.extraddl.model.ExtraDdlXmlReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
import java.util.ServiceLoader;
/**
* Generates DB Migration xml and sql scripts.
* Generates DDL migration scripts based on changes to the current model.
*
* <p>
* Reads the prior migrations and compares with the current model of the EbeanServer
* and generates a migration 'diff' in the form of xml document with the logical schema
* changes and a series of sql scripts to apply, rollback the applied changes if necessary
* and drop objects (drop tables, drop columns).
* </p>
* <p>
* This does not run the migration or ddl scripts but just generates them.
* Typically this is run as a main method in src/test once a developer is happy
* with the next set of changes to the model.
* </p>
*
* <h3>Example: Run for a single specific platform</h3>
*
* <pre>{@code
*
* DbMigration migration = new DbMigration();
* migration.setPathToResources("src/main/resources");
* migration.setPlatform(DbPlatformName.ORACLE);
* // optionally specify the version and name
* //System.setProperty("ddl.migration.version", "1.1");
* //System.setProperty("ddl.migration.name", "add bars");
*
* migration.generateMigration();
* // generate a migration using drops from a prior version
* //System.setProperty("ddl.migration.pendingDropsFor", "1.2");
*
* DbMigration migration = DbMigration.create();
*
* migration.setPlatform(Platform.POSTGRES);
* migration.generateMigration();
*
* }</pre>
*/
public class DbMigration {
protected static final Logger logger = LoggerFactory.getLogger(DbMigration.class);
private static final String initialVersion = "1.0";
private static final String GENERATED_COMMENT = "THIS IS A GENERATED FILE - DO NOT MODIFY";
public interface DbMigration {
/**
* Set to true if DbMigration run with online EbeanServer instance.
* Create a DbMigration implementation to use.
*/
protected final boolean online;
static DbMigration create() {
protected SpiEbeanServer server;
protected DbMigrationConfig migrationConfig;
protected String pathToResources = "src/main/resources";
protected DatabasePlatform databasePlatform;
protected List<Pair> platforms = new ArrayList<>();
protected ServerConfig serverConfig;
protected DbConstraintNaming constraintNaming;
/**
* Create for offline migration generation.
*/
public DbMigration() {
this.online = false;
}
/**
* Create using online EbeanServer.
*/
public DbMigration(EbeanServer server) {
this.online = true;
setServer(server);
Iterator<DbMigration> loader = ServiceLoader.load(DbMigration.class).iterator();
if (loader.hasNext()) {
return loader.next();
}
throw new IllegalStateException("No service implementation found for DbMigration?");
}
/**
@@ -105,33 +54,18 @@ public class DbMigration {
* <p>
* This defaults to maven style 'src/main/resources'.
*/
public void setPathToResources(String pathToResources) {
this.pathToResources = pathToResources;
}
void setPathToResources(String pathToResources);
/**
* Set the server to use to determine the current model.
* Typically this is not called explicitly.
*/
public void setServer(EbeanServer ebeanServer) {
this.server = (SpiEbeanServer) ebeanServer;
setServerConfig(server.getServerConfig());
}
void setServer(EbeanServer ebeanServer);
/**
* Set the serverConfig to use. Typically this is not called explicitly.
*/
public void setServerConfig(ServerConfig config) {
if (this.serverConfig == null) {
this.serverConfig = config;
}
if (migrationConfig == null) {
this.migrationConfig = serverConfig.getMigrationConfig();
}
if (constraintNaming == null) {
this.constraintNaming = serverConfig.getConstraintNaming();
}
}
void setServerConfig(ServerConfig config);
/**
* Set the specific platform to generate DDL for.
@@ -139,9 +73,7 @@ public class DbMigration {
* If not set this defaults to the platform of the default server.
* </p>
*/
public void setPlatform(Platform platform) {
setPlatform(getPlatform(platform));
}
void setPlatform(Platform platform);
/**
* Set the specific platform to generate DDL for.
@@ -149,12 +81,7 @@ public class DbMigration {
* If not set this defaults to the platform of the default server.
* </p>
*/
public void setPlatform(DatabasePlatform databasePlatform) {
this.databasePlatform = databasePlatform;
if (!online) {
DbOffline.setPlatform(databasePlatform.getPlatform());
}
}
void setPlatform(DatabasePlatform databasePlatform);
/**
* Add an additional platform to write the migration DDL.
@@ -163,9 +90,7 @@ public class DbMigration {
* from the migration (e.g. generate migration sql for MySql, Postgres and Oracle).
* </p>
*/
public void addPlatform(Platform platform, String prefix) {
platforms.add(new Pair(getPlatform(platform), prefix));
}
void addPlatform(Platform platform, String prefix);
/**
* Generate the next migration xml file and associated apply and rollback sql scripts.
@@ -175,388 +100,30 @@ public class DbMigration {
* <h3>Example: Run for a single specific platform</h3>
* <pre>{@code
*
* DbMigration migration = new DbMigration();
* migration.setPathToResources("src/main/resources");
* migration.setPlatform(DbPlatformName.ORACLE);
* DbMigration migration = DbMigration.create();
*
* migration.generateMigration();
* migration.setPlatform(Platform.POSTGRES);
*
* migration.generateMigration();
*
* }</pre>
* <p>
* <h3>Example: Run migration generating DDL for multiple platforms</h3>
* <pre>{@code
*
* DbMigration migration = new DbMigration();
* migration.setPathToResources("src/main/resources");
* DbMigration migration = DbMigration.create();
*
* migration.addPlatform(DbPlatformName.POSTGRES, "pg");
* migration.addPlatform(DbPlatformName.MYSQL, "mysql");
* migration.addPlatform(DbPlatformName.ORACLE, "mysql");
* migration.setPathToResources("src/main/resources");
*
* migration.generateMigration();
* migration.addPlatform(Platform.POSTGRES, "pg");
* migration.addPlatform(Platform.MYSQL, "mysql");
* migration.addPlatform(Platform.ORACLE, "oracle");
*
* migration.generateMigration();
*
* }</pre>
*
* @return the version of the generated migration or null
*/
public void generateMigration() throws IOException {
// use this flag to stop other plugins like full DDL generation
if (!online) {
DbOffline.setGenerateMigration();
if (databasePlatform == null && !platforms.isEmpty()) {
// for multiple platform generation set the general platform
// to H2 so that it runs offline without DB connection
setPlatform(platforms.get(0).platform);
}
}
setDefaults();
try {
Request request = createRequest();
if (platforms.isEmpty()) {
generateExtraDdl(request.migrationDir, databasePlatform);
}
String pendingVersion = generatePendingDrop();
if (pendingVersion != null) {
generatePendingDrop(request, pendingVersion);
} else {
generateDiff(request);
}
} finally {
if (!online) {
DbOffline.reset();
}
}
}
/**
* Generate "repeatable" migration scripts.
* <p>
* These take scrips from extra-dll.xml (typically views) and outputs "repeatable"
* migration scripts (starting with "R__") to be run by FlywayDb or Ebean's own
* migration runner.
* </p>
*/
private void generateExtraDdl(File migrationDir, DatabasePlatform dbPlatform) throws IOException {
if (dbPlatform != null) {
ExtraDdl extraDdl = ExtraDdlXmlReader.read("/extra-ddl.xml");
if (extraDdl != null) {
List<DdlScript> ddlScript = extraDdl.getDdlScript();
for (DdlScript script : ddlScript) {
if (ExtraDdlXmlReader.matchPlatform(dbPlatform.getName(), script.getPlatforms())) {
writeExtraDdl(migrationDir, script);
}
}
}
}
}
/**
* Write (or override) the "repeatable" migration script.
*/
private void writeExtraDdl(File migrationDir, DdlScript script) throws IOException {
String fullName = repeatableMigrationName(script.getName());
logger.info("writing repeatable script {}", fullName);
File file = new File(migrationDir, fullName);
try (FileWriter writer = new FileWriter(file)) {
writer.write(script.getValue());
writer.flush();
}
}
private String repeatableMigrationName(String scriptName) {
return "R__" + scriptName.replace(' ', '_') + migrationConfig.getApplySuffix();
}
/**
* Generate the diff migration.
*/
private void generateDiff(Request request) throws IOException {
List<String> pendingDrops = request.getPendingDrops();
if (!pendingDrops.isEmpty()) {
logger.info("Pending un-applied drops in versions {}", pendingDrops);
}
Migration migration = request.createDiffMigration();
if (migration == null) {
logger.info("no changes detected - no migration written");
} else {
// there were actually changes to write
generateMigration(request, migration, null);
}
}
/**
* Generate the migration based on the pendingDrops from a prior version.
*/
private void generatePendingDrop(Request request, String pendingVersion) throws IOException {
Migration migration = request.migrationForPendingDrop(pendingVersion);
generateMigration(request, migration, pendingVersion);
List<String> pendingDrops = request.getPendingDrops();
if (!pendingDrops.isEmpty()) {
logger.info("... remaining pending un-applied drops in versions {}", pendingDrops);
}
}
private Request createRequest() {
return new Request();
}
private class Request {
final File migrationDir;
final File modelDir;
final MigrationModel migrationModel;
final CurrentModel currentModel;
final ModelContainer migrated;
final ModelContainer current;
private Request() {
this.migrationDir = getMigrationDirectory();
this.modelDir = getModelDirectory(migrationDir);
this.migrationModel = new MigrationModel(modelDir, migrationConfig.getModelSuffix());
this.migrated = migrationModel.read();
this.currentModel = new CurrentModel(server, constraintNaming);
this.current = currentModel.read();
}
/**
* Return the migration for the pending drops for a given version.
*/
public Migration migrationForPendingDrop(String pendingVersion) {
Migration migration = migrated.migrationForPendingDrop(pendingVersion);
// register any remaining pending drops
migrated.registerPendingHistoryDropColumns(current);
return migration;
}
/**
* Return the list of versions that have pending un-applied drops.
*/
public List<String> getPendingDrops() {
return migrated.getPendingDrops();
}
/**
* Create and return the diff of the current model to the migration model.
*/
public Migration createDiffMigration() {
ModelDiff diff = new ModelDiff(migrated);
diff.compareTo(current);
return diff.isEmpty() ? null : diff.getMigration();
}
}
private void generateMigration(Request request, Migration dbMigration, String dropsFor) throws IOException {
String fullVersion = getFullVersion(request.migrationModel, dropsFor);
logger.info("generating migration:{}", fullVersion);
if (!writeMigrationXml(dbMigration, request.modelDir, fullVersion)) {
logger.warn("migration already exists, not generating DDL");
} else {
if (!platforms.isEmpty()) {
writeExtraPlatformDdl(fullVersion, request.currentModel, dbMigration, request.migrationDir);
} else if (databasePlatform != null) {
// writer needs the current model to provide table/column details for
// history ddl generation (triggers, history tables etc)
DdlWrite write = new DdlWrite(new MConfiguration(), request.current);
PlatformDdlWriter writer = createDdlWriter(databasePlatform, "");
writer.processMigration(dbMigration, write, request.migrationDir, fullVersion);
}
}
}
/**
* Return true if the next pending drop changeSet should be generated as the next migration.
*/
private String generatePendingDrop() {
String nextDrop = System.getProperty("ddl.migration.pendingDropsFor");
if (nextDrop != null) {
return nextDrop;
}
return migrationConfig.getGeneratePendingDrop();
}
/**
* Return the full version for the migration being generated.
* <p>
* The full version can contain a comment suffix after a "__" double underscore.
*/
private String getFullVersion(MigrationModel migrationModel, String dropsFor) {
String version = migrationConfig.getVersion();
if (version == null) {
version = migrationModel.getNextVersion(initialVersion);
}
String fullVersion = migrationConfig.getApplyPrefix() + version;
if (migrationConfig.getName() != null) {
fullVersion += "__" + toUnderScore(migrationConfig.getName());
} else if (dropsFor != null) {
fullVersion += "__" + toUnderScore("dropsFor_" + MigrationVersion.trim(dropsFor));
} else if (version.equals(initialVersion)) {
fullVersion += "__initial";
}
return fullVersion;
}
/**
* Replace spaces with underscores.
*/
private String toUnderScore(String name) {
return name.replace(' ', '_');
}
/**
* Write any extra platform ddl.
*/
protected void writeExtraPlatformDdl(String fullVersion, CurrentModel currentModel, Migration dbMigration, File writePath) throws IOException {
for (Pair pair : platforms) {
DdlWrite platformBuffer = new DdlWrite(new MConfiguration(), currentModel.read());
PlatformDdlWriter platformWriter = createDdlWriter(pair);
File subPath = platformWriter.subPath(writePath, pair.prefix);
platformWriter.processMigration(dbMigration, platformBuffer, subPath, fullVersion);
generateExtraDdl(subPath, pair.platform);
}
}
private PlatformDdlWriter createDdlWriter(Pair pair) {
return createDdlWriter(pair.platform, pair.prefix);
}
private PlatformDdlWriter createDdlWriter(DatabasePlatform platform, String prefix) {
return new PlatformDdlWriter(platform, serverConfig, prefix, migrationConfig);
}
/**
* Write the migration xml.
*/
protected boolean writeMigrationXml(Migration dbMigration, File resourcePath, String fullVersion) {
String modelFile = fullVersion + migrationConfig.getModelSuffix();
File file = new File(resourcePath, modelFile);
if (file.exists()) {
return false;
}
String comment = migrationConfig.isIncludeGeneratedFileComment() ? GENERATED_COMMENT : null;
MigrationXmlWriter xmlWriter = new MigrationXmlWriter(comment);
xmlWriter.write(dbMigration, file);
return true;
}
/**
* Set default server and platform if necessary.
*/
protected void setDefaults() {
if (server == null) {
setServer(Ebean.getDefaultServer());
}
if (databasePlatform == null && platforms.isEmpty()) {
// not explicitly set not set a list of platforms so
// default to the platform of the default server
databasePlatform = server.getDatabasePlatform();
logger.debug("set platform to {}", databasePlatform.getName());
}
}
/**
* Return the file path to write the xml and sql to.
*/
protected File getMigrationDirectory() {
// path to src/main/resources in typical maven project
File resourceRootDir = new File(pathToResources);
String resourcePath = migrationConfig.getMigrationPath();
// expect to be a path to something like - src/main/resources/dbmigration/model
File path = new File(resourceRootDir, resourcePath);
if (!path.exists()) {
if (!path.mkdirs()) {
logger.debug("Unable to ensure migration directory exists at {}", path.getAbsolutePath());
}
}
return path;
}
/**
* Return the model directory (relative to the migration directory).
*/
protected File getModelDirectory(File migrationDirectory) {
String modelPath = migrationConfig.getModelPath();
if (modelPath == null || modelPath.isEmpty()) {
return migrationDirectory;
}
File modelDir = new File(migrationDirectory, migrationConfig.getModelPath());
if (!modelDir.exists() && !modelDir.mkdirs()) {
logger.debug("Unable to ensure migration model directory exists at {}", modelDir.getAbsolutePath());
}
return modelDir;
}
/**
* Return the DatabasePlatform given the platform key.
*/
protected DatabasePlatform getPlatform(Platform platform) {
switch (platform) {
case H2:
return new H2Platform();
case POSTGRES:
return new PostgresPlatform();
case MYSQL:
return new MySqlPlatform();
case ORACLE:
return new OraclePlatform();
case SQLSERVER:
return new SqlServerPlatform();
case DB2:
return new DB2Platform();
case SQLITE:
return new SQLitePlatform();
default:
throw new IllegalArgumentException("Platform missing? " + platform);
}
}
/**
* Holds a platform and prefix. Used to generate multiple platform specific DDL
* for a single migration.
*/
public static class Pair {
/**
* The platform to generate the DDL for.
*/
public final DatabasePlatform platform;
/**
* A prefix included into the file/resource names indicating the platform.
*/
public final String prefix;
public Pair(DatabasePlatform platform, String prefix) {
this.platform = platform;
this.prefix = prefix;
}
}
String generateMigration() throws IOException;
}
@@ -1,15 +0,0 @@
package io.ebean.dbmigration.ddlgeneration.platform;
import io.ebean.config.dbplatform.DatabasePlatform;
/**
* DB2 platform specific DDL.
*/
public class DB2Ddl extends PlatformDdl {
public DB2Ddl(DatabasePlatform platform) {
super(platform);
this.identitySuffix = " generated by default as identity";
}
}
@@ -1,110 +0,0 @@
package io.ebean.dbmigration.ddlgeneration.platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.dbmigration.ddlgeneration.DdlBuffer;
import io.ebean.dbmigration.migration.AlterColumn;
import java.io.IOException;
/**
* MS SQL Server platform specific DDL.
*/
public class SqlServerDdl extends PlatformDdl {
public SqlServerDdl(DatabasePlatform platform) {
super(platform);
this.identitySuffix = " identity(1,1)";
this.foreignKeyRestrict = "";
this.inlineUniqueOneToOne = false;
this.columnSetDefault = "add default";
this.dropConstraintIfExists = "drop constraint";
this.historyDdl = new SqlServerHistoryDdl();
}
@Override
public String dropTable(String tableName) {
return "IF OBJECT_ID('" + tableName + "', 'U') IS NOT NULL drop table " + tableName;
}
@Override
public String alterTableDropForeignKey(String tableName, String fkName) {
return "IF OBJECT_ID('" + fkName + "', 'F') IS NOT NULL " + super.alterTableDropForeignKey(tableName, fkName);
}
/**
* MsSqlServer specific null handling on unique constraints.
*/
@Override
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns) {
// issues#233
String start = "create unique nonclustered index " + uqName + " on " + tableName + "(";
StringBuilder sb = new StringBuilder(start);
for (int i = 0; i < columns.length; i++) {
if (i > 0) {
sb.append(",");
}
sb.append(columns[i]);
}
sb.append(") where");
for (String column : columns) {
sb.append(" ").append(column).append(" is not null");
}
return sb.toString();
}
@Override
public String alterColumnDefaultValue(String tableName, String columnName, String defaultValue) {
if (isDropDefault(defaultValue)) {
return "-- alter table " + tableName + " drop constraint <unknown> -- find the appropriate constraint for default value on column " + columnName;
} else {
return "alter table " + tableName + " add default " + defaultValue + " for " + columnName;
}
}
@Override
public String alterColumnBaseAttributes(AlterColumn alter) {
String tableName = alter.getTableName();
String columnName = alter.getColumnName();
String type = alter.getType() != null ? alter.getType() : alter.getCurrentType();
boolean notnull = (alter.isNotnull() != null) ? alter.isNotnull() : Boolean.TRUE.equals(alter.isCurrentNotnull());
String notnullClause = notnull ? " not null" : "";
return "alter table " + tableName + " alter column " + columnName + " " + type + notnullClause;
}
@Override
public String alterColumnType(String tableName, String columnName, String type) {
// can't alter itself - done in alterColumnBaseAttributes()
return null;
}
@Override
public String alterColumnNotnull(String tableName, String columnName, boolean notnull) {
// can't alter itself - done in alterColumnBaseAttributes()
return null;
}
/**
* Add table comment as a separate statement (from the create table statement).
*/
@Override
public void addTableComment(DdlBuffer apply, String tableName, String tableComment) throws IOException {
// do nothing for MS SQL Server (cause it requires stored procedures etc)
}
/**
* Add column comment as a separate statement.
*/
@Override
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) throws IOException {
// do nothing for MS SQL Server (cause it requires stored procedures etc)
}
}
@@ -1,61 +0,0 @@
package io.ebean.dbmigration.ddlgeneration.platform;
import io.ebean.config.ServerConfig;
import io.ebean.dbmigration.ddlgeneration.DdlBuffer;
import io.ebean.dbmigration.ddlgeneration.DdlWrite;
import io.ebean.dbmigration.migration.AddHistoryTable;
import io.ebean.dbmigration.migration.DropHistoryTable;
import io.ebean.dbmigration.model.MTable;
import java.io.IOException;
/**
* @author Vilmos Nagy <vilmos.nagy@outlook.com>
*/
public class SqlServerHistoryDdl implements PlatformHistoryDdl {
private String systemPeriodStart;
private String systemPeriodEnd;
@Override
public void configure(ServerConfig serverConfig, PlatformDdl platformDdl) {
this.systemPeriodStart = serverConfig.getAsOfSysPeriod() + "From";
this.systemPeriodEnd = serverConfig.getAsOfSysPeriod() + "To";
}
@Override
public void createWithHistory(DdlWrite writer, MTable table) throws IOException {
String baseTable = table.getName();
enableSystemVersioning(writer, baseTable);
}
private void enableSystemVersioning(DdlWrite writer, String baseTable) throws IOException {
DdlBuffer apply = writer.applyHistory();
apply.append("alter table ").append(baseTable).newLine()
.append(" add ").append(systemPeriodStart).append(" datetime2 GENERATED ALWAYS AS ROW START NOT NULL DEFAULT SYSUTCDATETIME(),").newLine()
.append(" ").append(systemPeriodEnd).append(" datetime2 GENERATED ALWAYS AS ROW END NOT NULL,").newLine()
.append("period for system_time (").append(systemPeriodStart).append("From, ").append(systemPeriodEnd).append("To)").endOfStatement();
apply.append("alter table ").append(baseTable).append("set (system_versioning = on)").endOfStatement();
}
@Override
public void dropHistoryTable(DdlWrite writer, DropHistoryTable dropHistoryTable) throws IOException {
String baseTable = dropHistoryTable.getBaseTable();
DdlBuffer apply = writer.applyHistory();
apply.append("alter table ").append(baseTable).append("set (system_versioning = off)").endOfStatement();
apply.append("alter table ").append(baseTable).append(" drop column ").append(systemPeriodStart).endOfStatement();
apply.append("alter table ").append(baseTable).append(" drop column ").append(systemPeriodEnd).endOfStatement();
}
@Override
public void addHistoryTable(DdlWrite writer, AddHistoryTable addHistoryTable) throws IOException {
String baseTable = addHistoryTable.getBaseTable();
enableSystemVersioning(writer, baseTable);
}
@Override
public void updateTriggers(DdlWrite write, HistoryTableUpdate baseTable) throws IOException {
// SQL Server 2016 does not need triggers
}
}
@@ -1,8 +1,4 @@
/**
* Generation DDL migration scripts based on changes to the model.
* <p>
* You can use <code>DbMigration</code> to compare the entity bean model to its prior state
* and generate DDL for the differences - adding tables, columns etc.
* </p>
* Generates DDL migration scripts based on changes to the model.
*/
package io.ebean.dbmigration;
@@ -1,9 +1,5 @@
package io.ebean.event.changelog;
import io.ebean.ValuePair;
import java.util.Map;
/**
* A bean insert, update or delete change sent as part of a ChangeSet.
*/
@@ -12,68 +8,68 @@ public class BeanChange {
/**
* The underling base table name.
*/
String table;
private final String type;
/**
* The tenantId value.
*/
Object tenantId;
private final Object tenantId;
/**
* The id value.
*/
Object id;
private final Object id;
/**
* The INSERT, UPDATE or DELETE change type.
*/
ChangeType type;
private final ChangeType event;
/**
* The time the bean change was created.
*/
long eventTime;
private final long eventTime;
/**
* The values for insert or update. Note that null values are not included for insert.
* The change in JSON form.
*/
Map<String, ValuePair> values;
private final String data;
/**
* Construct with all the details.
* The change in JSON form.
*/
public BeanChange(String table, Object tenantId, Object id, ChangeType type, Map<String, ValuePair> values) {
this.table = table;
private final String oldData;
/**
* Construct with change as JSON.
*/
public BeanChange(String type, Object tenantId, Object id, ChangeType event, String data, String oldData) {
this.type = type;
this.tenantId = tenantId;
this.id = id;
this.type = type;
this.event = event;
this.eventTime = System.currentTimeMillis();
this.values = values;
this.data = data;
this.oldData = oldData;
}
/**
* Default constructor for JSON tools.
* Construct with change as JSON.
*/
public BeanChange() {
public BeanChange(String table, Object tenantId, Object id, ChangeType event, String data) {
this(table, tenantId , id , event , data , null);
}
@Override
public String toString() {
return "table:" + table + " tenantId: " + tenantId + " id:" + id + " values:" + values;
return "type:" + type + " tenantId: " + tenantId + " id:" + id + " data:" + data;
}
/**
* Return the object type (typically table name).
*/
public String getTable() {
return table;
}
/**
* Set the object type (for JSON tools).
*/
public void setTable(String table) {
this.table = table;
public String getType() {
return type;
}
/**
@@ -83,13 +79,6 @@ public class BeanChange {
return tenantId;
}
/**
* Set the bean id (for JSON tools).
*/
public void setTenantId(Object tenantId) {
this.tenantId = tenantId;
}
/**
* Return the object id.
*/
@@ -97,25 +86,11 @@ public class BeanChange {
return id;
}
/**
* Set the bean id (for JSON tools).
*/
public void setId(Object id) {
this.id = id;
}
/**
* Return the change type (INSERT, UPDATE or DELETE).
*/
public ChangeType getType() {
return type;
}
/**
* Set the type (for JSON tools).
*/
public void setType(ChangeType type) {
this.type = type;
public ChangeType getEvent() {
return event;
}
/**
@@ -126,23 +101,16 @@ public class BeanChange {
}
/**
* Set the event time in epoch millis.
* Return the change data in JSON form.
*/
public void setEventTime(long eventTime) {
this.eventTime = eventTime;
public String getData() {
return data;
}
/**
* Return the value pairs. For inserts the ValuePair oldValue is always null.
* Return the old data in JSON form.
*/
public Map<String, ValuePair> getValues() {
return values;
}
/**
* Set the value pairs (for JSON tools).
*/
public void setValues(Map<String, ValuePair> values) {
this.values = values;
public String getOldData() {
return oldData;
}
}
+1 -1
View File
@@ -35,7 +35,7 @@ Customer customer = Ebean.find(Customer.class)
.fetch("billingAddress");
.fetch("shippingAddress");
.setId(7)
.findUnique();
.findOne();
Address billAddr = customer.getBillingAddress();
+1 -1
View File
@@ -28,7 +28,7 @@ Customer customer =
.setId(7)
.fetch("billingAddress")
.fetch("shippingAddress")
.findUnique();
.findOne();
Address billAddr = customer.getBillingAddress();
Address shipAddr = customer.getShippingAddress();
+2 -2
View File
@@ -1,11 +1,11 @@
package io.ebean.plugin;
import io.ebean.Query;
import io.ebean.config.dbplatform.IdType;
import io.ebean.event.BeanFindController;
import io.ebean.event.BeanPersistController;
import io.ebean.event.BeanPersistListener;
import io.ebean.event.BeanQueryAdapter;
import io.ebeaninternal.api.SpiQuery;
import io.ebeanservice.docstore.api.mapping.DocumentMapping;
import java.util.Collection;
@@ -180,7 +180,7 @@ public interface BeanType<T> {
/**
* Add the discriminator value to the query if needed.
*/
void addInheritanceWhere(SpiQuery<?> query);
void addInheritanceWhere(Query<?> query);
/**
* Return the root bean type for an inheritance hierarchy.
@@ -1,4 +1,4 @@
package io.ebean.common;
package io.ebean.service;
import io.ebean.EbeanServer;
import io.ebean.config.ServerConfig;
@@ -0,0 +1,14 @@
package io.ebean.service;
import io.ebean.config.ContainerConfig;
/**
* Provides shutdown of the entire container.
*/
public interface SpiContainerFactory {
/**
* Create the Container that builds EbeanServer instances.
*/
SpiContainer create(ContainerConfig containerConfig);
}
@@ -0,0 +1,125 @@
package io.ebean.service;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* JSON service that Ebean is expected to provide.
*
* Supports converting between JSON content and simple java Maps/Lists.
*/
public interface SpiJsonService {
/**
* Write the nested Map/List as json.
*/
String write(Object object) throws IOException;
/**
* Write the nested Map/List as json to the writer.
*/
void write(Object object, Writer writer) throws IOException;
/**
* Write the nested Map/List as json to the jsonGenerator.
*/
void write(Object object, JsonGenerator jsonGenerator) throws IOException;
/**
* Write the collection as json array to the jsonGenerator.
*/
void writeCollection(Collection<Object> collection, JsonGenerator jsonGenerator) throws IOException;
/**
* Parse the json and return as a Map additionally specifying if the returned map should
* be modify aware meaning that it can detect when it has been modified.
*/
Map<String, Object> parseObject(String json, boolean modifyAware) throws IOException;
/**
* Parse the json and return as a Map.
*/
Map<String, Object> parseObject(String json) throws IOException;
/**
* Parse the json and return as a Map taking a reader.
*/
Map<String, Object> parseObject(Reader reader, boolean modifyAware) throws IOException;
/**
* Parse the json and return as a Map taking a reader.
*/
Map<String, Object> parseObject(Reader reader) throws IOException;
/**
* Parse the json and return as a Map taking a JsonParser.
*/
Map<String, Object> parseObject(JsonParser parser) throws IOException;
/**
* Parse the json and return as a Map taking a JsonParser and a starting token.
* <p>
* Used when the first token is checked to see if the value is null prior to calling this.
* </p>
*/
Map<String, Object> parseObject(JsonParser parser, JsonToken token) throws IOException;
/**
* Parse the json and return as a modify aware List.
*/
<T> List<T> parseList(String json, boolean modifyAware) throws IOException;
/**
* Parse the json and return as a List.
*/
List<Object> parseList(String json) throws IOException;
/**
* Parse the json and return as a List taking a Reader.
*/
List<Object> parseList(Reader reader) throws IOException;
/**
* Parse the json and return as a List taking a JsonParser.
*/
List<Object> parseList(JsonParser parser) throws IOException;
/**
* Parse the json returning as a List taking into account the current token.
*/
<T> List<T> parseList(JsonParser parser, JsonToken currentToken) throws IOException;
/**
* Parse the json and return as a List or Map.
*/
Object parse(String json) throws IOException;
/**
* Parse the json and return as a List or Map.
*/
Object parse(Reader reader) throws IOException;
/**
* Parse the json and return as a List or Map.
*/
Object parse(JsonParser parser) throws IOException;
/**
* Parse the json returning a Set that might be modify aware.
*/
<T> Set<T> parseSet(String json, boolean modifyAware) throws IOException;
/**
* Parse the json returning as a Set taking into account the current token.
*/
<T> Set<T> parseSet(JsonParser parser, JsonToken currentToken) throws IOException;
}
@@ -0,0 +1,27 @@
package io.ebean.service;
import io.ebean.RawSql;
import io.ebean.RawSqlBuilder;
import java.sql.ResultSet;
/**
* Service provided by Ebean for parsing and column mapping raw SQL queries.
*/
public interface SpiRawSqlService {
/**
* Create based on a JDBC ResultSet.
*/
RawSql resultSet(ResultSet resultSet, String... propertyNames);
/**
* Parse the SQL determining column mapping.
*/
RawSqlBuilder parsed(String sql);
/**
* Unparsed SQL so explicit column mapping expected.
*/
RawSqlBuilder unparsed(String sql);
}
@@ -2,7 +2,7 @@ package io.ebean.text;
import io.ebean.FetchPath;
import io.ebean.Query;
import io.ebeaninternal.server.query.SplitName;
import io.ebean.util.SplitName;
import java.util.Collection;
import java.util.Iterator;
@@ -89,11 +89,7 @@ public class PathProperties implements FetchPath {
}
Props getProps(String path) {
Props props = pathMap.get(path);
if (props == null) {
props = new Props(this, null, path);
pathMap.put(path, props);
}
Props props = pathMap.computeIfAbsent(path, p -> new Props(this, null, p));
return props;
}
@@ -27,16 +27,17 @@ public final class TimeStringParser implements StringParser {
}
String s = value.trim();
int minute;
int second;
int firstColon = s.indexOf(':');
int secondColon = s.indexOf(':', firstColon + 1);
if (firstColon == -1) {
throw new java.lang.IllegalArgumentException("No ':' in value [" + s + "]");
}
try {
int second;
int minute;
int hour = Integer.parseInt(s.substring(0, firstColon));
int secondColon = s.indexOf(':', firstColon + 1);
if (secondColon == -1) {
minute = Integer.parseInt(s.substring(firstColon + 1, s.length()));
second = 0;
+38 -30
View File
@@ -1,17 +1,18 @@
package io.ebean.text.json;
import io.ebeaninternal.server.type.ModifyAwareList;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import io.ebean.service.SpiJsonService;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
/**
@@ -19,32 +20,43 @@ import java.util.Set;
*/
public class EJson {
private static SpiJsonService plugin = init();
private static SpiJsonService init() {
Iterator<SpiJsonService> loader = ServiceLoader.load(SpiJsonService.class).iterator();
if (loader.hasNext()) {
return loader.next();
}
throw new IllegalStateException("No service implementation found for SpiJsonService?");
}
/**
* Write the nested Map/List as json.
*/
public static String write(Object object) throws IOException {
return EJsonWriter.write(object);
return plugin.write(object);
}
/**
* Write the nested Map/List as json to the writer.
*/
public static void write(Object object, Writer writer) throws IOException {
EJsonWriter.write(object, writer);
plugin.write(object, writer);
}
/**
* Write the nested Map/List as json to the jsonGenerator.
*/
public static void write(Object object, JsonGenerator jsonGenerator) throws IOException {
EJsonWriter.write(object, jsonGenerator);
plugin.write(object, jsonGenerator);
}
/**
* Write the collection as json array to the jsonGenerator.
*/
public static void writeCollection(Collection<Object> collection, JsonGenerator jsonGenerator) throws IOException {
EJsonWriter.writeCollection(collection, jsonGenerator);
plugin.writeCollection(collection, jsonGenerator);
}
/**
@@ -52,35 +64,35 @@ public class EJson {
* be modify aware meaning that it can detect when it has been modified.
*/
public static Map<String, Object> parseObject(String json, boolean modifyAware) throws IOException {
return EJsonReader.parseObject(json, modifyAware);
return plugin.parseObject(json, modifyAware);
}
/**
* Parse the json and return as a Map.
*/
public static Map<String, Object> parseObject(String json) throws IOException {
return EJsonReader.parseObject(json);
return plugin.parseObject(json);
}
/**
* Parse the json and return as a Map taking a reader.
*/
public static Map<String, Object> parseObject(Reader reader, boolean modifyAware) throws IOException {
return EJsonReader.parseObject(reader, modifyAware);
return plugin.parseObject(reader, modifyAware);
}
/**
* Parse the json and return as a Map taking a reader.
*/
public static Map<String, Object> parseObject(Reader reader) throws IOException {
return EJsonReader.parseObject(reader);
return plugin.parseObject(reader);
}
/**
* Parse the json and return as a Map taking a JsonParser.
*/
public static Map<String, Object> parseObject(JsonParser parser) throws IOException {
return EJsonReader.parseObject(parser);
return plugin.parseObject(parser);
}
/**
@@ -90,81 +102,77 @@ public class EJson {
* </p>
*/
public static Map<String, Object> parseObject(JsonParser parser, JsonToken token) throws IOException {
return EJsonReader.parseObject(parser, token);
return plugin.parseObject(parser, token);
}
/**
* Parse the json and return as a modify aware List.
*/
public static List<Object> parseList(String json, boolean modifyAware) throws IOException {
return EJsonReader.parseList(json, modifyAware);
public static <T> List<T> parseList(String json, boolean modifyAware) throws IOException {
return plugin.parseList(json, modifyAware);
}
/**
* Parse the json and return as a List.
*/
public static List<Object> parseList(String json) throws IOException {
return EJsonReader.parseList(json);
return plugin.parseList(json);
}
/**
* Parse the json and return as a List taking a Reader.
*/
public static List<Object> parseList(Reader reader) throws IOException {
return EJsonReader.parseList(reader);
return plugin.parseList(reader);
}
/**
* Parse the json and return as a List taking a JsonParser.
*/
public static List<Object> parseList(JsonParser parser) throws IOException {
return EJsonReader.parseList(parser, false);
return plugin.parseList(parser);
}
/**
* Parse the json returning as a List taking into account the current token.
*/
@SuppressWarnings("unchecked")
public static List<Object> parseList(JsonParser parser, JsonToken currentToken) throws IOException {
return (List<Object>) EJsonReader.parse(parser, currentToken, false);
public static <T> List<T> parseList(JsonParser parser, JsonToken currentToken) throws IOException {
return plugin.parseList(parser, currentToken);
}
/**
* Parse the json and return as a List or Map.
*/
public static Object parse(String json) throws IOException {
return EJsonReader.parse(json);
return plugin.parse(json);
}
/**
* Parse the json and return as a List or Map.
*/
public static Object parse(Reader reader) throws IOException {
return EJsonReader.parse(reader);
return plugin.parse(reader);
}
/**
* Parse the json and return as a List or Map.
*/
public static Object parse(JsonParser parser) throws IOException {
return EJsonReader.parse(parser);
return plugin.parse(parser);
}
/**
* Parse the json returning a Set that might be modify aware.
*/
public static Set parseSet(String json, boolean modifyAware) throws IOException {
List<Object> list = parseList(json, modifyAware);
if (list == null) {
return null;
}
return ((ModifyAwareList) list).asSet();
public static <T> Set<T> parseSet(String json, boolean modifyAware) throws IOException {
return plugin.parseSet(json, modifyAware);
}
/**
* Parse the json returning as a Set taking into account the current token.
*/
public static Set<Object> parseSet(JsonParser parser, JsonToken currentToken) throws IOException {
return new LinkedHashSet<>(parseList(parser, currentToken));
public static <T> Set<T> parseSet(JsonParser parser, JsonToken currentToken) throws IOException {
return plugin.parseSet(parser, currentToken);
}
}
@@ -0,0 +1,270 @@
package io.ebean.util;
import io.ebean.annotation.Formula;
import io.ebean.annotation.Platform;
import io.ebean.annotation.Where;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Annotation utility methods to find annotations recursively - taken from the spring framework.
*/
public class AnnotationUtil {
/**
* Determine if the supplied {@link Annotation} is defined in the core JDK {@code java.lang.annotation} package.
*/
public static boolean isInJavaLangAnnotationPackage(Annotation annotation) {
return annotation.annotationType().getName().startsWith("java.lang.annotation");
}
/**
* Find a single {@link Annotation} of {@code annotationType} on the supplied {@link AnnotatedElement}.
* <p>
* Meta-annotations will be searched if the annotation is not <em>directly present</em> on the supplied element.
* <p>
* <strong>Warning</strong>: this method operates generically on annotated elements. In other words, this method
* does not execute specialized search algorithms for classes or methods. It only traverses through Annotations!
* It also does not filter out platform dependent annotations!
*/
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.
A ann = annotatedElement.getAnnotation(annotationType);
if (ann != null) {
return ann;
} else {
return findAnnotation(annotatedElement, annotationType, new HashSet<>());
}
}
/**
* Find a single {@link Annotation} of {@code annotationType} on the supplied class.
* <p>Meta-annotations will be searched if the annotation is not directly present on
* the supplied element.
* <p><strong>Note</strong>: this method searches for annotations at class & superClass(es)!
*/
public static <A extends Annotation> A findAnnotationRecursive(Class<?> clazz, Class<A> annotationType) {
if (annotationType == null) {
return null;
}
// check if directly present, if not, start search for meta-annotations.
A ann = clazz.getAnnotation(annotationType);
if (ann != null) {
return ann;
} else {
while (clazz != null && clazz != Object.class) {
ann = findAnnotation(clazz, annotationType, new HashSet<>());
if (ann != null) {
return ann;
}
// no meta-annotation present at this class - traverse to superclass
clazz = clazz.getSuperclass();
}
return null;
}
}
/**
* Finds the first annotation of a type for this platform. (if annotation is platform specific, otherwise first
* found annotation is returned)
*/
public static <A extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType, Platform platform) {
if (annotationType == null) {
return null;
}
Set<A> anns = findAnnotations(annotatedElement, annotationType);
return getPlatformMatchingAnnotation(anns, platform);
}
/**
* Finds all annotations recusively for a class and its superclasses.
*/
public static <A extends Annotation> Set<A> findAnnotationsRecursive(Class<?> clazz, Class<A> annotationType) {
if (annotationType == null) {
return null;
}
Set<A> ret = new LinkedHashSet<>();
Set<Annotation> visited = new HashSet<>();
while (clazz != null && clazz != Object.class) {
findMetaAnnotations(clazz, annotationType, ret, visited);
clazz = clazz.getSuperclass();
}
return ret;
}
/**
* Perform the search algorithm avoiding endless recursion by tracking which
* annotations have already been visited.
*/
@SuppressWarnings("unchecked")
private static <A extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType, Set<Annotation> visited) {
Annotation[] anns = annotatedElement.getAnnotations(); // directly annotatated or inherited
for (Annotation ann : anns) {
if (ann.annotationType() == annotationType) {
return (A) ann;
}
}
for (Annotation ann : anns) {
if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) {
A annotation = findAnnotation(ann.annotationType(), annotationType, visited);
if (annotation != null) {
return annotation;
}
}
}
return null;
}
/**
* Find all {@link Annotation}s of {@code annotationType} on the supplied {@link AnnotatedElement}.
* <p>
* Meta-annotations will be searched if the annotation is not <em>directly present</em> on the supplied element.
* <p>
* <strong>Warning</strong>: this method operates generically on annotated elements. In other words, this method
* does not execute specialized search algorithms for classes or methods. It only traverses through Annotations!
*/
public static <A extends Annotation> Set<A> findAnnotations(AnnotatedElement annotatedElement, Class<A> annotationType) {
if (annotationType == null) {
return null;
}
Set<A> ret = new LinkedHashSet<>();
findMetaAnnotations(annotatedElement, annotationType, ret, new HashSet<>());
return ret;
}
/**
* Perform the search algorithm avoiding endless recursion by tracking which
* annotations have already been visited.
*/
@SuppressWarnings("unchecked")
private static <A extends Annotation> void findMetaAnnotations(AnnotatedElement annotatedElement, Class<A> annotationType, Set<A> ret, Set<Annotation> visited) {
Annotation[] anns = annotatedElement.getAnnotations();
for (Annotation ann : anns) {
if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) {
if (ann.annotationType() == annotationType) {
ret.add((A) ann);
} else {
Method repeatableValueMethod = getRepeatableValueMethod(ann, annotationType);
if (repeatableValueMethod != null) {
try {
A[] repeatedAnns = (A[]) repeatableValueMethod.invoke(ann);
for (Annotation repeatedAnn : repeatedAnns) {
ret.add((A) repeatedAnn);
findMetaAnnotations(repeatedAnn.annotationType(), annotationType, ret, visited);
}
} catch (Exception e) { // catch all exceptions (thrown by invoke)
throw new RuntimeException(e);
}
} else {
findMetaAnnotations(ann.annotationType(), annotationType, ret, visited);
}
}
}
}
}
// caches for getRepeatableValueMethod
private static Method getNullMethod() {
try {
return AnnotationUtil.class.getDeclaredMethod("getNullMethod");
} catch (NoSuchMethodException e) {
return null;
}
}
private static final ConcurrentMap<Annotation, Method> valueMethods = new ConcurrentHashMap<>();
// only a non-null-marker the valueMethods - Cache
private static final Method nullMethod = getNullMethod();
/**
* Returns the <code>value()</code> method for a possible containerAnnotation.
* Method is retuned only, if its signature is <code>array of containingType</code>.
*/
private static <A extends Annotation> Method getRepeatableValueMethod(
Annotation containerAnnotation, Class<A> containingType) {
Method method = valueMethods.get(containerAnnotation);
if (method == null) {
try {
method = containerAnnotation.annotationType().getMethod("value");
} catch (NoSuchMethodException e) {
method = nullMethod;
} catch (Exception e) {
throw new RuntimeException(e);
}
Method prev = valueMethods.putIfAbsent(containerAnnotation, method);
method = prev == null ? method : prev;
}
if (method != nullMethod) {
Class<?> retType = method.getReturnType();
if (retType.isArray() && retType.getComponentType() == containingType) {
return method;
}
}
return null;
}
/**
* Finds a suitable annotation from <code>Set<T> anns</code> for this platform.
* To distinguish between platforms, annotation type <code>T</code> must define
* a method withthis signature:
* <p>
* <code>Class<? extends DatabasePlatform>[] platforms() default {};</code>
* </p>
* The finding rules are:
* <ol>
* <li>Check if T has method "platforms" if not, return <code>ann[0]</code></code>
* <li>find the annotation that is defined for <code>databasePlatform</code></li>
* <li>otherwise return the annotation for default platform (platforms = {})</li>
* <li>return null
* </ol>
* (This mechanism is currently used by {@link Where} and {@link Formula})
*/
public static <T extends Annotation> T getPlatformMatchingAnnotation(Set<T> anns, Platform matchPlatform) {
if (anns.isEmpty()) {
return null;
}
Method getPlatformsMethod = null;
T fallback = null;
for (T ann : anns) {
try {
if (getPlatformsMethod == null) {
getPlatformsMethod = ann.getClass().getMethod("platforms");
}
if (!Platform[].class.isAssignableFrom(getPlatformsMethod.getReturnType())) {
return ann;
}
Platform[] platforms = (Platform[]) getPlatformsMethod.invoke(ann);
if (platforms.length == 0) {
fallback = ann;
} else {
for (Platform platform : platforms) {
if (matchPlatform == platform) {
return ann;
}
}
}
} catch (NoSuchMethodException e) {
return ann; // not platform specific - return first one
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return fallback;
}
}
@@ -12,7 +12,7 @@ public class CamelCaseHelper {
String[] vals = underscore.split("_");
if (vals.length == 1) {
return underscore;
return isUpperCase(underscore) ? underscore.toLowerCase() : underscore;
}
StringBuilder result = new StringBuilder();
@@ -29,4 +29,13 @@ public class CamelCaseHelper {
return result.toString();
}
private static boolean isUpperCase(String underscore) {
for (int i = 0; i < underscore.length(); i++) {
if (Character.isLowerCase(underscore.charAt(i))) {
return false;
}
}
return true;
}
}
@@ -1,47 +0,0 @@
package io.ebean.util;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
/**
* Helper to find classes taking into account the context class loader.
*/
public class ClassUtil {
/**
* Returns the raw type for the 2nd generic parameter for a subclass.
*/
public static Class<?> getSecondArgumentType(Class<?> subclass) {
Type[] typeArguments = getSuperclassTypeParameter(subclass);
if (typeArguments.length != 2) {
throw new IllegalArgumentException("Expected type with 2 generic argument types but got "
+ typeArguments.length + " - " + Arrays.toString(typeArguments));
}
return getRawType(typeArguments[1]);
}
static Type[] getSuperclassTypeParameter(Class<?> subclass) {
Type superclass = subclass.getGenericSuperclass();
if (superclass instanceof Class) {
throw new RuntimeException("Missing generics type parameters on subclass " + subclass);
}
return ((ParameterizedType) superclass).getActualTypeArguments();
}
private static Class<?> getRawType(Type type) {
if (type instanceof Class<?>) {
return (Class<?>) type;
} else if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
Type rawType = parameterizedType.getRawType();
if (rawType instanceof Class<?>) {
return (Class<?>) rawType;
}
}
throw new RuntimeException("Unable to obtain raw class type from " + type);
}
}
@@ -1,4 +1,4 @@
package io.ebeaninternal.server.query;
package io.ebean.util;
/**
* Helper for dot notation property paths.
+128 -192
View File
@@ -3,6 +3,7 @@ package io.ebean.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Utility String class that supports String manipulation functions.
@@ -13,6 +14,10 @@ public class StringHelper {
private static final char DOUBLE_QUOTE = '"';
private static final Pattern SPLIT_NAMES = Pattern.compile("[\\s,;]+");
private static final String[] EMPTY_STRING_ARRAY = new String[0];
/**
* parses a String of the form name1='value1' name2='value2'. Note that you
* can use either single or double quotes for any particular name value pair
@@ -48,42 +53,44 @@ public class StringHelper {
*/
private static HashMap<String, String> parseNameQuotedValue(HashMap<String, String> map,
String tag, int pos) throws RuntimeException {
while (true) {
int equalsPos = tag.indexOf('=', pos);
if (equalsPos > -1) {
// check for begin quote...
char firstQuote = tag.charAt(equalsPos + 1);
if (firstQuote != SINGLE_QUOTE && firstQuote != DOUBLE_QUOTE) {
throw new RuntimeException("missing begin quote at " + (equalsPos) + "["
+ tag.charAt(equalsPos + 1) + "] in [" + tag + "]");
int equalsPos = tag.indexOf('=', pos);
if (equalsPos > -1) {
// check for begin quote...
char firstQuote = tag.charAt(equalsPos + 1);
if (firstQuote != SINGLE_QUOTE && firstQuote != DOUBLE_QUOTE) {
throw new RuntimeException("missing begin quote at " + (equalsPos) + "["
+ tag.charAt(equalsPos + 1) + "] in [" + tag + "]");
}
// check for end quote...
int endQuotePos = tag.indexOf(firstQuote, equalsPos + 2);
if (endQuotePos == -1) {
throw new RuntimeException("missing end quote [" + firstQuote + "] after " + pos + " in [" + tag + "]");
}
// we have a valid name and value...
// dp("pos="+pos+" equalsPos="+equalsPos+"
// endQuotePos="+endQuotePos);
String name = tag.substring(pos, equalsPos);
// dp("name="+name+"; value="+value+";");
// trim off any whitespace from the front of name...
name = trimFront(name, " ");
if ((name.indexOf(SINGLE_QUOTE) > -1) || (name.indexOf(DOUBLE_QUOTE) > -1)) {
throw new RuntimeException("attribute name contains a quote [" + name + "]");
}
String value = tag.substring(equalsPos + 2, endQuotePos);
map.put(name, value);
pos = endQuotePos + 1;
} else {
// no more equals... stop parsing...
return map;
}
// check for end quote...
int endQuotePos = tag.indexOf(firstQuote, equalsPos + 2);
if (endQuotePos == -1) {
throw new RuntimeException("missing end quote [" + firstQuote + "] after " + pos + " in [" + tag + "]");
}
// we have a valid name and value...
// dp("pos="+pos+" equalsPos="+equalsPos+"
// endQuotePos="+endQuotePos);
String name = tag.substring(pos, equalsPos);
// dp("name="+name+"; value="+value+";");
// trim off any whitespace from the front of name...
name = trimFront(name, " ");
if ((name.indexOf(SINGLE_QUOTE) > -1) || (name.indexOf(DOUBLE_QUOTE) > -1)) {
throw new RuntimeException("attribute name contains a quote [" + name + "]");
}
String value = tag.substring(equalsPos + 2, endQuotePos);
map.put(name, value);
return parseNameQuotedValue(map, tag, endQuotePos + 1);
} else {
// no more equals... stop parsing...
return map;
}
}
@@ -96,14 +103,15 @@ public class StringHelper {
}
private static int countOccurances(String content, String occurs, int pos, int countSoFar) {
int equalsPos = content.indexOf(occurs, pos);
if (equalsPos > -1) {
countSoFar += 1;
pos = equalsPos + occurs.length();
// dp("countSoFar="+countSoFar+" pos="+pos);
return countOccurances(content, occurs, pos, countSoFar);
} else {
return countSoFar;
while (true) {
int equalsPos = content.indexOf(occurs, pos);
if (equalsPos > -1) {
countSoFar += 1;
pos = equalsPos + occurs.length();
// dp("countSoFar="+countSoFar+" pos="+pos);
} else {
return countSoFar;
}
}
}
@@ -135,14 +143,16 @@ public class StringHelper {
* @param trim the string to trim off the front
*/
public static String trimFront(String source, String trim) {
if (source == null) {
return null;
}
if (source.indexOf(trim) == 0) {
// dp("trim ...");
return trimFront(source.substring(trim.length()), trim);
} else {
return source;
while (true) {
if (source == null) {
return null;
}
if (source.indexOf(trim) == 0) {
// dp("trim ...");
source = source.substring(trim.length());
} else {
return source;
}
}
}
@@ -158,55 +168,57 @@ public class StringHelper {
*/
private static HashMap<String, String> getKeyValue(HashMap<String, String> map, int pos,
String allNameValuePairs, String listDelimiter, String nameValueSeparator) {
while (true) {
if (pos >= allNameValuePairs.length()) {
// dp("end as "+pos+" >= "+allNameValuePairs.length() );
return map;
}
int equalsPos = allNameValuePairs.indexOf(nameValueSeparator, pos);
int delimPos = allNameValuePairs.indexOf(listDelimiter, pos);
if (delimPos == -1) {
delimPos = allNameValuePairs.length();
}
if (equalsPos == -1) {
// dp("no more equals...");
return map;
}
if (delimPos == (equalsPos + 1)) {
// dp("Ignoring as nothing between delim and equals...
// delim:"+delimPos+" eq:"+equalsPos);
return getKeyValue(map, delimPos + 1, allNameValuePairs, listDelimiter,
nameValueSeparator);
}
if (equalsPos > delimPos) {
// there is a key without a value?
String key = allNameValuePairs.substring(pos, delimPos);
key = key.trim();
if (!key.isEmpty()) {
map.put(key, null);
if (pos >= allNameValuePairs.length()) {
// dp("end as "+pos+" >= "+allNameValuePairs.length() );
return map;
}
return getKeyValue(map, delimPos + 1, allNameValuePairs, listDelimiter,
nameValueSeparator);
}
String key = allNameValuePairs.substring(pos, equalsPos);
int equalsPos = allNameValuePairs.indexOf(nameValueSeparator, pos);
int delimPos = allNameValuePairs.indexOf(listDelimiter, pos);
if (delimPos > -1) {
String value = allNameValuePairs.substring(equalsPos + 1, delimPos);
// dp("cont "+key+","+value+" pos:"+pos+"
// len:"+allNameValuePairs.length());
key = key.trim();
if (delimPos == -1) {
delimPos = allNameValuePairs.length();
}
if (equalsPos == -1) {
// dp("no more equals...");
return map;
}
if (delimPos == (equalsPos + 1)) {
// dp("Ignoring as nothing between delim and equals...
// delim:"+delimPos+" eq:"+equalsPos);
pos = delimPos + 1;
continue;
}
if (equalsPos > delimPos) {
// there is a key without a value?
String key = allNameValuePairs.substring(pos, delimPos);
key = key.trim();
if (!key.isEmpty()) {
map.put(key, null);
}
pos = delimPos + 1;
continue;
map.put(key, value);
pos = delimPos + 1;
}
String key = allNameValuePairs.substring(pos, equalsPos);
// recurse the rest of the values...
return getKeyValue(map, pos, allNameValuePairs, listDelimiter, nameValueSeparator);
} else {
// dp("ERROR: delimPos < 0 ???");
return map;
if (delimPos > -1) {
String value = allNameValuePairs.substring(equalsPos + 1, delimPos);
// dp("cont "+key+","+value+" pos:"+pos+"
// len:"+allNameValuePairs.length());
key = key.trim();
map.put(key, value);
pos = delimPos + 1;
// recurse the rest of the values...
} else {
// dp("ERROR: delimPos < 0 ???");
return map;
}
}
}
@@ -258,66 +270,6 @@ public class StringHelper {
}
}
/**
* This returns the FIRST string in str that is bounded on the left by
* leftBound, and bounded on the right by rightBound. This will return null if
* the leftBound is not found within str.
* <p>
* If leftBound can't be found this returns null.
* <p>
* This rightBound can't be found then this throws a
* StringIndexOutOfBoundsException.
*
* @param str the base string that we will search for the bounded string.
* @param leftBound the left bound of the string.
* @param rightBound the right bound of the string.
*/
public static String getBoundedString(String str, String leftBound, String rightBound)
throws RuntimeException {
if (str == null) {
throw new RuntimeException("string to parse is null?");
}
int startPos = str.indexOf(leftBound);
if (startPos > -1) {
startPos += leftBound.length();
int endPos = str.indexOf(rightBound, startPos);
// dp(str+" start:"+startPos+" end:"+endPos);
if (endPos == -1) {
throw new RuntimeException("Can't find rightBound: " + rightBound);
}
return str.substring(startPos, endPos);
} else {
// if no leftBound can be found.. return null... could be in a
// search n parse type loop?
// this keeps "no tag"==null different from "tag not formed
// properly"==StringParsingException
return null;
}
}
/**
* Takes the String bounded by leftBound & rightBound, and replaces it with
* replaceString. Actually removes the left and right bound strings aswell.
*/
public static String setBoundedString(String str, String leftBound, String rightBound,
String replaceString) {
int startPos = str.indexOf(leftBound);
if (startPos > -1) {
// startPos = startPos;
int endPos = str.indexOf(rightBound, startPos + leftBound.length());
if (endPos > -1) {
String toReplace = str.substring(startPos, endPos + 1);
return replaceString(str, toReplace, replaceString);
} else {
return str;
}
} else {
return str;
}
}
/**
* This method takes a String and will replace all occurrences of the match
* String with that of the replace String.
@@ -423,6 +375,9 @@ public class StringHelper {
* }</pre>
*/
public static String replaceStringMulti(String source, String[] match, String replace) {
if (source == null) {
return null;
}
return replaceStringMulti(source, match, replace, 30, 0, source.length());
}
@@ -436,7 +391,9 @@ public class StringHelper {
*/
public static String replaceStringMulti(String source, String[] match, String replace,
int additionalSize, int startPos, int endPos) {
if (source == null) {
return null;
}
int shortestMatch = match[0].length();
char[] match0 = new char[match.length];
@@ -496,48 +453,27 @@ public class StringHelper {
}
/**
* This method takes a String as an argument and removes all occurrences of
* the supplied Char. It returns the resulting String.
* Splits at any whitespace "," or ";" and trims the result.
* It does not return empty entries.
*/
public static String removeChar(String s, char chr) {
StringBuilder sb = new StringBuilder(s.length());
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c != chr) {
sb.append(c);
}
public static String[] splitNames(String names) {
if (names == null || names.isEmpty()) {
return EMPTY_STRING_ARRAY;
}
return sb.toString();
}
/**
* This method takes a String as an argument and removes all occurrences of
* the supplied Chars. It returns the resulting String.
*/
public static String removeChars(String s, char[] chr) {
StringBuilder sb = new StringBuilder(s.length());
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (!charMatch(c, chr)) {
sb.append(c);
}
String[] result = SPLIT_NAMES.split(names);
if (result.length == 0) {
return EMPTY_STRING_ARRAY; // don't know if this ever can happen
}
return sb.toString();
}
private static boolean charMatch(int iChr, char[] chr) {
for (char aChr : chr) {
if (iChr == aChr) {
return true;
if ("".equals(result[0])) { // = input string starts with whitespace
if (result.length == 1) { // = input string contains only whitespace
return EMPTY_STRING_ARRAY;
} else {
String ret[] = new String[result.length-1]; // remove first entry
System.arraycopy(result, 1, ret, 0, ret.length);
return ret;
}
} else {
return result;
}
return false;
}
}
@@ -35,7 +35,7 @@ public class BindParams implements Serializable {
* Bind hash and count used to detect when the bind values have changed such
* that the generated SQL (with named parameters) needs to be recalculated.
*/
private int[] bindHash;
private String bindHash;
public BindParams() {
}
@@ -56,32 +56,29 @@ public class BindParams implements Serializable {
* taken into account when calculating the query hash.
* </p>
*/
public void buildQueryPlanHash(HashQueryPlanBuilder builder) {
int[] vals = calcQueryPlanHash();
builder.add(vals[0]).bind(vals[1]);
public String calcQueryPlanHash() {
StringBuilder builder = new StringBuilder();
buildQueryPlanHash(builder);
return builder.toString();
}
/**
* Calculate and return a query plan bind hash with total bind count.
*/
public int[] calcQueryPlanHash() {
public void buildQueryPlanHash(StringBuilder builder) {
int tempBindCount;
int bc = 0;
int hc = 92821;
for (Param param : positionedParameters) {
tempBindCount = param.queryBindCount();
bc += tempBindCount;
hc = hc * 92821 + tempBindCount;
builder.append("p").append(bc).append(" ?:").append(tempBindCount).append(",");
}
for (Map.Entry<String, Param> entry : namedParameters.entrySet()) {
tempBindCount = entry.getValue().queryBindCount();
bc += tempBindCount;
hc = hc * 92821 + entry.getKey().hashCode();
hc = hc * 92821 + tempBindCount;
builder.append("n").append(bc).append(" k:").append(entry.getKey()).append(" ?:").append(tempBindCount).append(",");
}
return new int[]{hc, bc};
}
/**
@@ -169,11 +166,7 @@ public class BindParams implements Serializable {
}
private Param getParam(String name) {
Param p = namedParameters.get(name);
if (p == null) {
p = new Param();
namedParameters.put(name, p);
}
Param p = namedParameters.computeIfAbsent(name, k -> new Param());
return p;
}
@@ -281,9 +274,9 @@ public class BindParams implements Serializable {
bindHash = calcQueryPlanHash();
return false;
}
int[] oldPlan = bindHash;
String oldPlan = bindHash;
bindHash = calcQueryPlanHash();
return bindHash[0] == oldPlan[0] && bindHash[1] == oldPlan[1];
return bindHash.equals(oldPlan);
}
/**
@@ -1,4 +1,4 @@
package io.ebean.plugin;
package io.ebeaninternal.api;
import io.ebean.config.ServerConfig;
import io.ebeaninternal.server.type.ScalarType;
@@ -3,7 +3,7 @@ package io.ebeaninternal.api;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.el.ElPropertyDeploy;
import io.ebeaninternal.server.query.SplitName;
import io.ebean.util.SplitName;
import io.ebeaninternal.server.query.SqlJoinType;
import java.io.Serializable;
@@ -1,7 +1,7 @@
package io.ebeaninternal.api;
import io.ebean.TxScope;
import io.ebean.PersistBatch;
import io.ebean.annotation.PersistBatch;
import java.util.ArrayList;
@@ -53,6 +53,8 @@ public class ScopeTrans implements Thread.UncaughtExceptionHandler {
private Boolean restoreBatchGeneratedKeys;
private boolean restoreBatchFlushOnQuery;
/**
* Flag set when a rollback has occurred.
*/
@@ -77,10 +79,14 @@ public class ScopeTrans implements Thread.UncaughtExceptionHandler {
restoreBatchOnCascade = transaction.getBatchOnCascade();
restoreBatchSize = transaction.getBatchSize();
restoreBatchGeneratedKeys = transaction.getBatchGetGeneratedKeys();
restoreBatchFlushOnQuery = transaction.isBatchFlushOnQuery();
}
if (txScope.isBatchSet()) {
transaction.setBatch(txScope.getBatch());
}
if (!txScope.isFlushOnQuery()) {
transaction.setBatchFlushOnQuery(false);
}
if (txScope.isBatchOnCascadeSet()) {
transaction.setBatchOnCascade(txScope.getBatchOnCascade());
}
@@ -147,7 +153,7 @@ public class ScopeTrans implements Thread.UncaughtExceptionHandler {
}
protected void restoreSuspended() {
if (suspendedTransaction != null) {
if (created || suspendedTransaction != null) {
// put the previously suspended transaction
// back onto the ThreadLocal or equivalent
scopeMgr.replace(suspendedTransaction);
@@ -158,6 +164,7 @@ public class ScopeTrans implements Thread.UncaughtExceptionHandler {
if (created) {
transaction.commit();
} else {
transaction.setBatchFlushOnQuery(restoreBatchFlushOnQuery);
if (restoreBatch != null) {
transaction.setBatch(restoreBatch);
}
@@ -1,6 +1,6 @@
package io.ebeaninternal.api;
import io.ebean.PersistBatch;
import io.ebean.annotation.PersistBatch;
import io.ebean.TransactionCallback;
import io.ebean.annotation.DocStoreMode;
import io.ebean.bean.PersistenceContext;
@@ -13,7 +13,6 @@ import io.ebeaninternal.server.persist.BatchControl;
import io.ebeanservice.docstore.api.DocStoreTransaction;
import javax.persistence.PersistenceException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
@@ -0,0 +1,18 @@
package io.ebeaninternal.api;
/**
* Plugin API available to invoke something prior to container bootup.
* <p>
* The initial intent is to provide a hook for 'docker-run' such that we can automatically ensure
* we have a test DB docker container running and setup ready to go.
* </p>
*/
public interface SpiContainerBootup {
/**
* Run something at bootup prior to the container starting.
*
* For example, start DB docker container(s).
*/
void bootup();
}
@@ -13,6 +13,7 @@ import io.ebean.config.ServerConfig;
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.timezone.DataTimeZone;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.query.CQuery;
@@ -25,6 +26,11 @@ import java.util.List;
*/
public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionLoader {
/**
* Return the server extended Json context.
*/
SpiJsonContext jsonExtended();
/**
* For internal use, shutdown of the server invoked by JVM Shutdown.
*/
@@ -200,4 +206,14 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
* Return the DataTimeZone to use when reading/writing timestamps via JDBC.
*/
DataTimeZone getDataTimeZone();
/**
* Check for slow query event.
*/
void slowQueryCheck(long executionTimeMicros, int rowCount, SpiQuery<?> query);
/**
* Create DDL handler given the platform and configuration of the server.
*/
DdlHandler createDdlHandler();
}
@@ -48,18 +48,13 @@ public interface SpiExpression extends Expression {
* from an AutoTune perspective and get different tuning.
* </p>
*/
void queryPlanHash(HashQueryPlanBuilder builder);
void queryPlanHash(StringBuilder builder);
/**
* Return the hash value for the values that will be bound.
*/
int queryBindHash();
/**
* Return true if the expression is the same without taking into account bind values.
*/
boolean isSameByPlan(SpiExpression other);
/**
* Return true if the expression is the same with respect to bind values.
*/
@@ -65,4 +65,9 @@ public interface SpiExpressionRequest {
* Append a DB Like clause.
*/
void appendLike();
/**
* Escapes a string to use it as exact match in Like clause.
*/
String escapeLikeString(String value);
}
@@ -0,0 +1,24 @@
package io.ebeaninternal.api;
import com.fasterxml.jackson.core.JsonGenerator;
import io.ebean.text.json.JsonContext;
import io.ebean.text.json.JsonWriteOptions;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import java.io.Writer;
/**
* Extended Json Context for internal server use.
*/
public interface SpiJsonContext extends JsonContext {
/**
* Create a Json Writer for writing beans as JSON.
*/
SpiJsonWriter createJsonWriter(JsonGenerator gen, JsonWriteOptions options);
/**
* Create a Json Writer for writing beans as JSON supplying a writer.
*/
SpiJsonWriter createJsonWriter(Writer writer);
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.api;
import io.ebean.CacheMode;
import io.ebean.EbeanServer;
import io.ebean.ExpressionList;
import io.ebean.OrderBy;
@@ -19,6 +20,7 @@ import io.ebeaninternal.server.query.CancelableQuery;
import io.ebeaninternal.server.querydefn.NaturalKeyBindParam;
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import io.ebeaninternal.server.querydefn.OrmUpdateProperties;
import io.ebeaninternal.server.rawsql.SpiRawSql;
import java.sql.Timestamp;
import java.util.List;
@@ -161,6 +163,7 @@ public interface SpiQuery<T> extends Query<T> {
/**
* Return the ForUpdate mode.
*/
@Override
ForUpdate getForUpdateMode();
/**
@@ -168,6 +171,11 @@ public interface SpiQuery<T> extends Query<T> {
*/
BeanDescriptor<T> getBeanDescriptor();
/**
* Return the RawSql that was set to use for this query.
*/
SpiRawSql getRawSql();
/**
* Return true if this query should be executed against the doc store.
*/
@@ -582,9 +590,9 @@ public interface SpiQuery<T> extends Query<T> {
boolean isUseBeanCache();
/**
* Return true if this query should use/check the query cache.
* Return the cache mode if this query should use/check the query cache.
*/
boolean isUseQueryCache();
CacheMode getUseQueryCache();
/**
* Return true if the beans from this query should be loaded into the bean
@@ -1,6 +1,6 @@
package io.ebean.dbmigration;
package io.ebeaninternal.dbmigration;
import io.ebean.Platform;
import io.ebean.annotation.Platform;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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