mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
164
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d8bc0a2ce | ||
|
|
e182190f96 | ||
|
|
8711ebc6f7 | ||
|
|
baa7242939 | ||
|
|
47e8d27212 | ||
|
|
70aed3906e | ||
|
|
70c3860a65 | ||
|
|
1ba21d5c43 | ||
|
|
2019bc7bf1 | ||
|
|
3cb52917e3 | ||
|
|
55a257a64c | ||
|
|
83ef204f3c | ||
|
|
a18cac035b | ||
|
|
873c14faaf | ||
|
|
e1af7c29eb | ||
|
|
c1c85b67c2 | ||
|
|
05b8adbf6e | ||
|
|
3ca4efc818 | ||
|
|
ec2c3b13d4 | ||
|
|
5a3e386b7c | ||
|
|
7e56a147be | ||
|
|
62cdfb3209 | ||
|
|
740532da4b | ||
|
|
130356cb3b | ||
|
|
ab84cfae52 | ||
|
|
ad21b7a3fd | ||
|
|
31581082d2 | ||
|
|
aa84dfe7d8 | ||
|
|
aad5b33db2 | ||
|
|
5801f7620d | ||
|
|
514416a1e0 | ||
|
|
8faa95adc1 | ||
|
|
ebaeafc11b | ||
|
|
98e014f8c8 | ||
|
|
78912d75e0 | ||
|
|
09786a7062 | ||
|
|
54530248a5 | ||
|
|
fdafa776e1 | ||
|
|
392eb2744d | ||
|
|
b642b3c644 | ||
|
|
0e1c815826 | ||
|
|
83b463040f | ||
|
|
aea7d937eb | ||
|
|
78628ffad0 | ||
|
|
56a742ad70 | ||
|
|
a4415b03ff | ||
|
|
e252f71792 | ||
|
|
18ca3403ac | ||
|
|
94a118d635 | ||
|
|
efe325034b | ||
|
|
0c368ed7b3 | ||
|
|
c1b022c123 | ||
|
|
021d57f4b2 | ||
|
|
11e180aa3f | ||
|
|
9e2ddc9f8c | ||
|
|
d4e72eefa0 | ||
|
|
525afbc00f | ||
|
|
6f2e1c0248 | ||
|
|
0d0a3422fc | ||
|
|
57b0190cd2 | ||
|
|
c8e199177c | ||
|
|
68a470a086 | ||
|
|
45a023f0e4 | ||
|
|
06bce35f5a | ||
|
|
3dfdd0e2e8 | ||
|
|
a569c7fc96 | ||
|
|
e751f991cb | ||
|
|
83cf768369 | ||
|
|
bb418456e3 | ||
|
|
ac9f3408b2 | ||
|
|
78f8d48e2e | ||
|
|
3f8b9529e1 | ||
|
|
10f3d85d9f | ||
|
|
0e3f47ee82 | ||
|
|
cdbbd749a3 | ||
|
|
a712be2917 | ||
|
|
0cba0664f8 | ||
|
|
a3e5c3a786 | ||
|
|
58def02cd1 | ||
|
|
cec65cc888 | ||
|
|
da7bb834e7 | ||
|
|
a64a244745 | ||
|
|
1c19d4cf7e | ||
|
|
347225efb2 | ||
|
|
91cb5a23fd | ||
|
|
0e219a9bc6 | ||
|
|
21182da793 | ||
|
|
0de084caec | ||
|
|
e3425a8850 | ||
|
|
8e9843ab8a | ||
|
|
75462e7410 | ||
|
|
265bda8cf2 | ||
|
|
7d0ed261b5 | ||
|
|
ff111eaf33 | ||
|
|
8b18e907af | ||
|
|
40704e2ef7 | ||
|
|
0e62d0579e | ||
|
|
47d55a2f29 | ||
|
|
b037be5d3e | ||
|
|
60a3c24cba | ||
|
|
4a243a76a7 | ||
|
|
9aee7c892d | ||
|
|
18d944f228 | ||
|
|
29bb9d3b48 | ||
|
|
f8ea5619d0 | ||
|
|
e3fac7c622 | ||
|
|
8ee68ac5e1 | ||
|
|
ec7b376612 | ||
|
|
f2d438f21a | ||
|
|
20bd2241e4 | ||
|
|
dd907cc4bc | ||
|
|
4d122ea05b | ||
|
|
83384c6f0e | ||
|
|
eaa48468cd | ||
|
|
0b1c3a4233 | ||
|
|
3e8b06e38e | ||
|
|
81d48d67a7 | ||
|
|
8d0df6541e | ||
|
|
2be3099d55 | ||
|
|
183d079b58 | ||
|
|
40634412f5 | ||
|
|
b8aa822a30 | ||
|
|
a1d0fbb194 | ||
|
|
70ccc6588d | ||
|
|
3d62e84d59 | ||
|
|
ac36172cc6 | ||
|
|
1829c74694 | ||
|
|
cc7e22014d | ||
|
|
a5c6f483b0 | ||
|
|
5237e8ba66 | ||
|
|
9926cad7f1 | ||
|
|
6c1b295d41 | ||
|
|
49b568e7d4 | ||
|
|
628af31ddd | ||
|
|
31cac60393 | ||
|
|
6a067c0cf7 | ||
|
|
842292ef6b | ||
|
|
6db9a60f4f | ||
|
|
d9125db957 | ||
|
|
4bd9e67703 | ||
|
|
2a986ab590 | ||
|
|
7c477827fe | ||
|
|
b2967dec1e | ||
|
|
a95fd063ae | ||
|
|
17a54e3804 | ||
|
|
f1b2a09b30 | ||
|
|
e4d9e4f2de | ||
|
|
6474f306b5 | ||
|
|
f1c2f134ad | ||
|
|
0f7158ea18 | ||
|
|
3fdb76d18f | ||
|
|
b1027a2f25 | ||
|
|
e5a1f9f7cc | ||
|
|
f78f24136a | ||
|
|
5b2aa0c74d | ||
|
|
175714046a | ||
|
|
73b2cc55f3 | ||
|
|
cebe375856 | ||
|
|
2f47235018 | ||
|
|
17abd6d977 | ||
|
|
cc5f3689f3 | ||
|
|
abcc651e7f | ||
|
|
caa36262c1 | ||
|
|
66b6dba921 |
@@ -10,3 +10,4 @@ indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
spaces_around_operators = true
|
||||
max_line_length = 130
|
||||
|
||||
@@ -20,3 +20,4 @@ profiling/
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
*uuid.state
|
||||
+11
@@ -9,16 +9,27 @@ git:
|
||||
|
||||
addons:
|
||||
postgresql: "9.6"
|
||||
# apt:
|
||||
# packages:
|
||||
# - mysql-server-5.7
|
||||
# - mysql-client-core-5.7
|
||||
# - mysql-client-5.7
|
||||
|
||||
services:
|
||||
- docker
|
||||
- postgresql
|
||||
- mysql
|
||||
|
||||
before_script:
|
||||
- ./.travis/setup_database
|
||||
- mysql -u root -e 'CREATE DATABASE test_ebean;'
|
||||
- mysql -u root -e "CREATE USER 'test_ebean'@'localhost' IDENTIFIED BY 'test';"
|
||||
- mysql -u root -e "GRANT ALL ON test_ebean.* TO 'test_ebean'@'localhost';"
|
||||
|
||||
env:
|
||||
- EBEAN_DB=h2
|
||||
- EBEAN_DB=pg
|
||||
# - EBEAN_DB=mysql
|
||||
|
||||
install: true
|
||||
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
Overview of ebean.properties file
|
||||
=================================
|
||||
|
||||
|
||||
### DbMigration options
|
||||
|
||||
You can set individual values for different platforms when generating migrations
|
||||
|
||||
`dbmigration.platform.<PLATFORM>.databaseSequenceBatchSize`
|
||||
For DB's using sequences this is the number of sequence values prefetched.
|
||||
|
||||
`dbmigration.platform.<PLATFORM>.dbuuid`
|
||||
Control, how UUID generation should work - it affects DDL which column type is generated. Possible values:
|
||||
- BINARY enforces binary UUID
|
||||
- VARCHAR enforces varchar UUID
|
||||
- BINARY_OPTIMIZED enforces binary-optimized UUID (makes sense only with Type1 ID)
|
||||
- AUTO_BINARY use binary when platform does not support native UUID
|
||||
- AUTO_BINARY_OPTIMIZED use binary-optimized when platform does not support native UUID
|
||||
- AUTO_VARCHAR (default) use varchar when platform does not support UUID
|
||||
|
||||
`dbmigration.platform.<PLATFORM>.uuidStoreAsBinary`
|
||||
Same as setting dbuuid to BINARY
|
||||
|
||||
`dbmigration.platform.<PLATFORM>.geometrySRID`
|
||||
The Geometry SRID value (default 4326).
|
||||
|
||||
`dbmigration.platform.<PLATFORM>.idType`
|
||||
The ID type (IDENTITY, SEQUENCE, GENERATOR, EXTERNAL)
|
||||
|
||||
`dbmigration.platform.<PLATFORM>.mapping`
|
||||
Adjust the mapping. For example `BOOLEAN=integer(32);BIT=tinyint(3)`
|
||||
|
||||
|
||||
`ebean.migration.applyPrefix`
|
||||
Set this to "V" to be compatible with FlywayDB.
|
||||
|
||||
`ebean.migration.applySuffix`
|
||||
Defaults to '.sql'
|
||||
|
||||
`ebean.migration.dbusername`
|
||||
DB user used to run the DB migration.
|
||||
|
||||
`ebean.migration.dbpassword`
|
||||
DB password used to run the DB migration.
|
||||
|
||||
`ebean.migration.generate`
|
||||
Set to true if the DB migration should be generated on server start.
|
||||
|
||||
`ebean.migration.generatePendingDrop`
|
||||
The version of a pending drop that should be generated as the next migration.
|
||||
|
||||
`ebean.migration.includeGeneratedFileComment`
|
||||
TODO
|
||||
|
||||
`ebean.migration.metaTable`
|
||||
For running migration the DB table that holds migration execution status. Default 'db_migration'
|
||||
|
||||
`ebean.migration.migrationPath`
|
||||
Resource path for the migration xml and sql.
|
||||
|
||||
`ebean.migration.modelPath`
|
||||
Subdirectory the model xml files go into. Default 'model'
|
||||
|
||||
`ebean.migration.modelSuffix`
|
||||
Suffix. Default '.model.xml'
|
||||
|
||||
`ebean.migration.name`
|
||||
Description text that can be appended to the version to become the ddl script file name
|
||||
|
||||
`ebean.migration.patchInsertOn`
|
||||
migration versions that should be added to history without running.
|
||||
|
||||
`ebean.migration.patchResetChecksumOn`
|
||||
migration versions that should have their checksum reset and not run.
|
||||
Use this if you get a 'Checksum mismatch' error.
|
||||
|
||||
`ebean.migration.placeholders`
|
||||
A comma and equals delimited placeholders that are substituted in SQL scripts when running migration (used by DB Migration runner only).
|
||||
|
||||
`ebean.migration.platform`
|
||||
The database platform to generate migration DDL for.
|
||||
|
||||
`ebean.migration.run`
|
||||
Flag set to true means to run any outstanding migrations on startup.
|
||||
|
||||
`ebean.migration.version`
|
||||
The migration version name (typically FlywayDb compatible). Example: 1.1.1_2
|
||||
|
||||
|
||||
### Ebean UUID options
|
||||
|
||||
`ebean.uuidVersion`
|
||||
Controls, how the UUIDs are generated. Possible values
|
||||
- VERSION4 (default) generate random V4 UUIDs,
|
||||
- VERSION1 generate rfc4122 compliant Type 1 UUIDs (requires a state file)
|
||||
- VERSION1RND generate fake Type 1 UUIDs
|
||||
|
||||
Note, that V1 UUIDs in conjunction with AUTO_BINARY_OPTIMIZED will give you the best index performance, but you MUST understand how this works to avoid collisions.
|
||||
|
||||
`ebean.uuidStateFile`
|
||||
The state file that is Required to generate V1 UUIDs
|
||||
|
||||
|
||||
### DocStoreConfig
|
||||
|
||||
`ebean.docstore.active`
|
||||
True when the Document store integration is active/on.
|
||||
|
||||
`ebean.docstore.allowAllCertificates`
|
||||
Set to true such that the client allows connections to invalid/self signed SSL certificates.
|
||||
|
||||
`ebean.docstore.bulkBatchSize`
|
||||
The default batch size to use for the Bulk API calls.
|
||||
|
||||
`ebean.docstore.create`
|
||||
When true the Document store should create any document indexes that don't already exist.
|
||||
|
||||
`ebean.docstore.dropCreate`
|
||||
When true the Document store should drop and re-create document indexes.
|
||||
|
||||
`ebean.docstore.generateMapping`
|
||||
Set to true means Ebean will generate mapping files on startup.
|
||||
|
||||
`ebean.docstore.mappingPath`
|
||||
Resource path for the Document store mapping files.
|
||||
|
||||
`ebean.docstore.mappingSuffix`
|
||||
Suffix used for mapping files.
|
||||
|
||||
`ebean.docstore.password`
|
||||
Password credential that be used for authentication to document store.
|
||||
|
||||
`ebean.docstore.pathToResources`
|
||||
Location of resources that mapping files are generated into.
|
||||
|
||||
`ebean.docstore.persist`
|
||||
The default mode used by indexes. `DEFAULT|IGNORE|QUEUE|UPDATE`
|
||||
|
||||
`ebean.docstore.url`
|
||||
The URL of the Document store server. For example: http://localhost:9200.
|
||||
|
||||
`ebean.docstore.username`
|
||||
Credential that be used for authentication to document store.
|
||||
|
||||
### Ebean Autotune
|
||||
|
||||
`ebean.autoTune.mode`
|
||||
The autoTune mode `DEFAULT_OFF|DEFAULT_ON|DEFAULT_ONIFEMPTY`
|
||||
|
||||
`ebean.autoTune.profiling`
|
||||
TODO
|
||||
|
||||
`ebean.autoTune.profilingBase`
|
||||
TODO
|
||||
|
||||
`ebean.autoTune.profilingFile`
|
||||
TODO
|
||||
|
||||
`ebean.autoTune.profilingRate`
|
||||
TODO
|
||||
|
||||
`ebean.autoTune.profilingUpdateFrequency`
|
||||
TODO
|
||||
|
||||
`ebean.autoTune.queryTuning`
|
||||
TODO
|
||||
|
||||
`ebean.autoTune.queryTuningAddVersion`
|
||||
TODO
|
||||
|
||||
`ebean.autoTune.queryTuningFile`
|
||||
TODO
|
||||
|
||||
`ebean.autoTune.skipGarbageCollectionOnShutdown`
|
||||
TODO
|
||||
|
||||
`ebean.autoTune.skipProfileReportingOnShutdown`
|
||||
TODO
|
||||
|
||||
|
||||
### Ebean options
|
||||
`ebean.allQuotedIdentifiers`
|
||||
Quote all identifiers
|
||||
|
||||
`ebean.asOfSysPeriod`
|
||||
Column used to support history and 'As of' queries. This column is a timestamp range or equivalent.
|
||||
|
||||
`ebean.asOfViewSuffix`
|
||||
Suffix appended to the base table to derive the view that contains the union of the base table and the history table in order to support asOf queries.
|
||||
|
||||
`ebean.autoCommitMode`
|
||||
Set to true if the DataSource uses autoCommit. Indicates that Ebean should use autoCommit friendly Transactions and TransactionManager.
|
||||
|
||||
`ebean.autoReadOnlyDataSource`
|
||||
When true create a read only DataSource using readOnlyDataSourceConfig defaulting values from dataSourceConfig
|
||||
|
||||
`ebean.autostart`
|
||||
Should the server start all
|
||||
|
||||
`ebean.backgroundExecutorSchedulePoolSize`
|
||||
TODO
|
||||
|
||||
`ebean.backgroundExecutorShutdownSecs`
|
||||
TODO
|
||||
|
||||
and some more todos:
|
||||
`ebean.batch.mode`
|
||||
`ebean.batch.size`
|
||||
`ebean.changeLogAsync`
|
||||
`ebean.changeLogIncludeInserts`
|
||||
`ebean.classes`
|
||||
`ebean.collectQueryOrigins`
|
||||
`ebean.collectQueryStatsByNode`
|
||||
`ebean.currentUserProvider`
|
||||
`ebean.dataSourceJndiName`
|
||||
`ebean.dataTimeZone`
|
||||
`ebean.databaseBooleanFalse`
|
||||
`ebean.databaseBooleanTrue`
|
||||
`ebean.databasePlatform`
|
||||
`ebean.databasePlatformName`
|
||||
`ebean.databaseSequenceBatchSize`
|
||||
ebean.datasource.h2.adminpassword
|
||||
ebean.datasource.h2.adminusername
|
||||
`ebean.dbEncrypt`
|
||||
`ebean.dbOffline`
|
||||
ebean.ddl.createOnly
|
||||
ebean.ddl.generate
|
||||
ebean.ddl.header
|
||||
ebean.ddl.initSql
|
||||
ebean.ddl.run
|
||||
ebean.ddl.seedSql
|
||||
ebean.defaultDeleteMissingChildren
|
||||
ebean.defaultOrderById
|
||||
ebean.disableClasspathSearch
|
||||
ebean.disableL2Cache
|
||||
ebean.docStoreOnly
|
||||
ebean.docstore.active
|
||||
ebean.docstore.allowAllCertificates
|
||||
ebean.docstore.bulkBatchSize
|
||||
ebean.docstore.create
|
||||
ebean.docstore.dropCreate
|
||||
ebean.docstore.generateMapping
|
||||
ebean.docstore.mappingPath
|
||||
ebean.docstore.mappingSuffix
|
||||
ebean.docstore.password
|
||||
ebean.docstore.pathToResources
|
||||
ebean.docstore.persist
|
||||
ebean.docstore.url
|
||||
ebean.docstore.username
|
||||
ebean.encryptDeployManager
|
||||
ebean.encryptKeyManager
|
||||
ebean.encryptor
|
||||
ebean.explicitTransactionBeginMode
|
||||
ebean.expressionEqualsWithNullAsNoop
|
||||
ebean.expressionNativeIlike
|
||||
ebean.geometrySRID
|
||||
ebean.historyTableSuffix
|
||||
ebean.jdbcFetchSizeFindEach
|
||||
ebean.jdbcFetchSizeFindList
|
||||
ebean.jodaLocalTimeMode
|
||||
ebean.jsonDateTime
|
||||
ebean.jsonInclude
|
||||
ebean.lazyLoadBatchSize
|
||||
ebean.localTimeWithNanos
|
||||
ebean.namingConvention.schema
|
||||
ebean.namingConvention.sequenceFormat
|
||||
ebean.namingConvention.useForeignKeyPrefix
|
||||
ebean.namingconvention
|
||||
ebean.notifyL2CacheInForeground
|
||||
ebean.packages
|
||||
ebean.persistBatch
|
||||
ebean.persistBatchOnCascade
|
||||
ebean.persistBatchSize
|
||||
ebean.persistBatching
|
||||
ebean.persistenceContextScope
|
||||
ebean.profiling
|
||||
ebean.profiling.directory
|
||||
ebean.profiling.includeProfileIds
|
||||
ebean.profiling.minimumMicros
|
||||
ebean.profiling.profilesPerFile
|
||||
ebean.profiling.verbose
|
||||
ebean.queryBatchSize
|
||||
ebean.queryPlanTTLSeconds
|
||||
ebean.search.packages
|
||||
ebean.serverCachePlugin
|
||||
ebean.skipCacheAfterWrite
|
||||
ebean.slowQueryMillis
|
||||
ebean.tenant.catalogProvider
|
||||
ebean.tenant.currentTenantProvider
|
||||
ebean.tenant.mode
|
||||
ebean.tenant.partitionColumn
|
||||
ebean.tenant.schemaProvider
|
||||
ebean.updateAllPropertiesInBatch
|
||||
ebean.updateChangesOnly
|
||||
ebean.updatesDeleteMissingChildren
|
||||
ebean.useJavaxValidationNotNull
|
||||
ebean.useJtaTransactionManager
|
||||
|
||||
+7
-6
@@ -48,7 +48,8 @@ Reqirements
|
||||
run: `mvn clean test -Ddatasource.default=pg`
|
||||
|
||||
Current status: PASS
|
||||
Tests run: 1986, Failures: 0, Errors: 0, Skipped: 16
|
||||
Tests run: 2166, Failures: 0, Errors: 0, Skipped: 25
|
||||
|
||||
|
||||
|
||||
'mysql' platform
|
||||
@@ -61,8 +62,8 @@ Reqirements
|
||||
|
||||
run: `mvn clean test -Ddatasource.default=mysql`
|
||||
|
||||
Current status: FAIL
|
||||
Tests run: 1986, Failures: 2, Errors: 4, Skipped: 16
|
||||
Current status: PASS
|
||||
Tests run: 2166, Failures: 0, Errors: 0, Skipped: 33
|
||||
|
||||
|
||||
|
||||
@@ -76,8 +77,8 @@ Reqires an installed sqlserver - e.g. https://hub.docker.com/r/microsoft/mssql-s
|
||||
|
||||
run: `mvn clean test -Ddatasource.default=mssql`
|
||||
|
||||
Current status: FAIL
|
||||
Tests run: 1986, Failures: 19, Errors: 43, Skipped: 16
|
||||
Current status: PASS
|
||||
Tests run: 2166, Failures: 0, Errors: 0, Skipped: 39
|
||||
|
||||
|
||||
|
||||
@@ -107,7 +108,7 @@ Reqires a locally installed DB2 Express-C server.
|
||||
run: `mvn clean test -Ddatasource.default=db2 -Pdb2`
|
||||
|
||||
Current status: FAIL
|
||||
Tests run: 1986, Failures: 33, Errors: 26, Skipped: 16
|
||||
Tests run: 2166, Failures: 29, Errors: 58, Skipped: 30
|
||||
|
||||
|
||||
'sqlite' platform
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean</artifactId>
|
||||
<version>11.10.2</version>
|
||||
<version>11.14.2</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>ebean</name>
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<scm>
|
||||
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
|
||||
<tag>ebean-11.10.2</tag>
|
||||
<tag>ebean-11.14.2</tag>
|
||||
</scm>
|
||||
|
||||
<profiles>
|
||||
@@ -83,6 +83,15 @@
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!--
|
||||
Exclude snakeyaml if you desire - it is not strictly required.
|
||||
-->
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
<version>1.19</version>
|
||||
</dependency>
|
||||
|
||||
<!--
|
||||
Class retention Nonnull and Nullable annotations
|
||||
to assist with IDE auto-completion with Ebean API
|
||||
@@ -103,7 +112,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-annotation</artifactId>
|
||||
<version>3.6</version>
|
||||
<version>3.8</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -127,7 +136,7 @@
|
||||
<dependency>
|
||||
<groupId>org.antlr</groupId>
|
||||
<artifactId>antlr4-runtime</artifactId>
|
||||
<version>4.6</version>
|
||||
<version>4.7.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Jackson core used internally by Ebean -->
|
||||
@@ -191,12 +200,12 @@
|
||||
<!--<scope>test</scope>-->
|
||||
<!--</dependency>-->
|
||||
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>microsoft</groupId>-->
|
||||
<!--<artifactId>sqlserver-jdbc</artifactId>-->
|
||||
<!--<version>4.2</version>-->
|
||||
<!--<scope>test</scope>-->
|
||||
<!--</dependency>-->
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-docker-run</artifactId>
|
||||
<version>1.5.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
@@ -215,7 +224,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-agent</artifactId>
|
||||
<version>11.9.1</version>
|
||||
<version>11.10.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -288,7 +297,7 @@
|
||||
<plugin>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-maven-plugin</artifactId>
|
||||
<version>11.9.1</version>
|
||||
<version>11.10.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>test</id>
|
||||
@@ -321,6 +330,11 @@
|
||||
<name>datasource.default</name>
|
||||
<value>${datasource.default}</value>
|
||||
</property>
|
||||
<property>
|
||||
<!-- transfer dbClockDelta parameter -->
|
||||
<name>dbClockDelta</name>
|
||||
<value>${dbClockDelta}</value>
|
||||
</property>
|
||||
</systemProperties>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
package io.ebean;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Provides finder functionality for use with "Dependency Injection style" use of Ebean.
|
||||
* <p>
|
||||
* Note that typically users would extend BeanRepository rather than BeanFinder.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* public class CustomerFinder extends BeanFinder<Long,Customer> {
|
||||
*
|
||||
* @Inject
|
||||
* public CustomerFinder(EbeanServer server) {
|
||||
* super(Customer.class, server);
|
||||
* }
|
||||
*
|
||||
* // ... add customer specific finders
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param <I> The ID type
|
||||
* @param <T> The Bean type
|
||||
*/
|
||||
public abstract class BeanFinder<I,T> {
|
||||
|
||||
protected final EbeanServer server;
|
||||
|
||||
protected final Class<T> type;
|
||||
|
||||
/**
|
||||
* Create with the given bean type and EbeanServer instance.
|
||||
*
|
||||
* @param type The bean type
|
||||
* @param server The EbeanServer instance typically created via Spring factory or equivalent.
|
||||
*/
|
||||
protected BeanFinder(Class<T> type, EbeanServer server) {
|
||||
this.type = type;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the EbeanServer to use.
|
||||
*/
|
||||
public EbeanServer db() {
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current transaction.
|
||||
*/
|
||||
public Transaction currentTransaction() {
|
||||
return db().currentTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush the JDBC batch on the current transaction.
|
||||
*/
|
||||
public void flush() {
|
||||
db().flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return typically a different EbeanServer to the default.
|
||||
* <p>
|
||||
* This is equivalent to {@link Ebean#getServer(String)}
|
||||
*
|
||||
* @param server The name of the EbeanServer. If this is null then the default EbeanServer is
|
||||
* returned.
|
||||
*/
|
||||
public EbeanServer db(String server) {
|
||||
return Ebean.getServer(server);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an entity reference for this ID.
|
||||
* <p>
|
||||
* Equivalent to {@link EbeanServer#getReference(Class, Object)}
|
||||
*/
|
||||
@Nonnull
|
||||
public T ref(I id) {
|
||||
return db().getReference(type, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an entity by ID.
|
||||
* <p>
|
||||
* Equivalent to {@link EbeanServer#find(Class, Object)}
|
||||
*/
|
||||
@Nullable
|
||||
public T findById(I id) {
|
||||
return db().find(type, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an entity by ID returning an Optional.
|
||||
*/
|
||||
@Nullable
|
||||
public Optional<T> findByIdOrEmpty(I id) {
|
||||
return db().find(type).setId(id).findOneOrEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a bean by Id.
|
||||
* <p>
|
||||
* Equivalent to {@link EbeanServer#delete(Class, Object)}
|
||||
*/
|
||||
public void deleteById(I id) {
|
||||
db().delete(type, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all entities of the given type.
|
||||
*/
|
||||
@Nonnull
|
||||
public List<T> findAll() {
|
||||
return query().findList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an update query.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* int rows =
|
||||
* updateQuery()
|
||||
* .set("status", Customer.Status.ACTIVE)
|
||||
* .set("updtime", new Timestamp(System.currentTimeMillis()))
|
||||
* .where()
|
||||
* .gt("id", 1000)
|
||||
* .update();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* Equivalent to {@link EbeanServer#update(Class)}
|
||||
*/
|
||||
protected UpdateQuery<T> updateQuery() {
|
||||
return db().update(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a query.
|
||||
* <p>
|
||||
* Equivalent to {@link EbeanServer#find(Class)}
|
||||
*/
|
||||
protected Query<T> query() {
|
||||
return db().find(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a native sql query.
|
||||
*/
|
||||
protected Query<T> nativeSql(String nativeSql) {
|
||||
return db().findNative(type, nativeSql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a query using the ORM query language.
|
||||
*/
|
||||
protected Query<T> query(String ormQuery) {
|
||||
return db().createQuery(type, ormQuery);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package io.ebean;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
|
||||
/**
|
||||
* Provides finder functionality for use with "Dependency Injection style" use of Ebean.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* @Repository
|
||||
* public class CustomerRepository extends BeanRepository<Long,Customer> {
|
||||
*
|
||||
* @Inject
|
||||
* public CustomerRepository(EbeanServer server) {
|
||||
* super(Customer.class, server);
|
||||
* }
|
||||
*
|
||||
* // ... add customer specific finders and persist logic
|
||||
*
|
||||
* public List<Customer> findByName(String nameStart) {
|
||||
* return query().where()
|
||||
* .istartsWith("name", nameStart)
|
||||
* .findList();
|
||||
* }
|
||||
*
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* @param <I> The ID type
|
||||
* @param <T> The Bean type
|
||||
*/
|
||||
public abstract class BeanRepository<I, T> extends BeanFinder<I, T> {
|
||||
|
||||
/**
|
||||
* Create with the given bean type and EbeanServer instance.
|
||||
* <p>
|
||||
* Typically users would extend BeanRepository rather than BeanFinder.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* @Inject
|
||||
* public CustomerRepository(EbeanServer server) {
|
||||
* super(Customer.class, server);
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param type The bean type
|
||||
* @param server The EbeanServer instance typically created via Spring factory or equivalent
|
||||
*/
|
||||
protected BeanRepository(Class<T> type, EbeanServer server) {
|
||||
super(type, server);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the entity bean as dirty.
|
||||
* <p>
|
||||
* This is used so that when a bean that is otherwise unmodified is updated the version
|
||||
* property is updated.
|
||||
* <p>
|
||||
* An unmodified bean that is saved or updated is normally skipped and this marks the bean as
|
||||
* dirty so that it is not skipped.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* Customer customer = customerRepository.byId(id);
|
||||
*
|
||||
* // mark the bean as dirty so that a save() or update() will
|
||||
* // increment the version property
|
||||
*
|
||||
* customerRepository.markAsDirty(customer);
|
||||
* customerRepository.save(customer);
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see EbeanServer#markAsDirty(Object)
|
||||
*/
|
||||
public void markAsDirty(T bean) {
|
||||
db().markAsDirty(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the property as unset or 'not loaded'.
|
||||
* <p>
|
||||
* This would be used to specify a property that we did not wish to include in a stateless update.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // populate an entity bean from JSON or whatever
|
||||
* Customer customer = ...;
|
||||
*
|
||||
* // mark the email property as 'unset' so that it is not
|
||||
* // included in a 'stateless update'
|
||||
* customerRepository.markPropertyUnset(customer, "email");
|
||||
*
|
||||
* customerRepository.update(customer);
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param propertyName the name of the property on the bean to be marked as 'unset'
|
||||
*/
|
||||
public void markPropertyUnset(T bean, String propertyName) {
|
||||
((EntityBean) bean)._ebean_getIntercept().setPropertyLoaded(propertyName, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert or update this entity depending on its state.
|
||||
* <p>
|
||||
* Ebean will detect if this is a new bean or a previously fetched bean and perform either an
|
||||
* insert or an update based on that.
|
||||
*
|
||||
* @see EbeanServer#save(Object)
|
||||
*/
|
||||
public void save(T bean) {
|
||||
db().save(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update this entity.
|
||||
*
|
||||
* @see EbeanServer#update(Object)
|
||||
*/
|
||||
public void update(T bean) {
|
||||
db().update(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert this entity.
|
||||
*
|
||||
* @see EbeanServer#insert(Object)
|
||||
*/
|
||||
public void insert(T bean) {
|
||||
db().insert(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete this bean.
|
||||
* <p>
|
||||
* This will return true if the bean was deleted successfully or JDBC batch is being used.
|
||||
* </p>
|
||||
* <p>
|
||||
* If there is no current transaction one will be created and committed for
|
||||
* you automatically.
|
||||
* </p>
|
||||
* <p>
|
||||
* If the Bean does not have a version property (or loaded version property) and
|
||||
* the bean does not exist then this returns false indicating that nothing was
|
||||
* deleted. Note that, if JDBC batch mode is used then this always returns true.
|
||||
* </p>
|
||||
*
|
||||
* @see EbeanServer#delete(Object)
|
||||
*/
|
||||
public boolean delete(T bean) {
|
||||
return db().delete(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a bean permanently without soft delete.
|
||||
* <p>
|
||||
* This is used when the bean contains a <code>@SoftDelete</code> property and we
|
||||
* want to perform a hard/permanent delete.
|
||||
* </p>
|
||||
*
|
||||
* @see EbeanServer#deletePermanent(Object)
|
||||
*/
|
||||
public boolean deletePermanent(T bean) {
|
||||
return db().deletePermanent(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes this entity from the database.
|
||||
*
|
||||
* @see EbeanServer#refresh(Object)
|
||||
*/
|
||||
public void refresh(T bean) {
|
||||
db().refresh(bean);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.ebean;
|
||||
/**
|
||||
* Enumeration to use with {@link Query#setCountDistinct(CountDistinctOrder)}.
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*
|
||||
*/
|
||||
public enum CountDistinctOrder {
|
||||
NO_ORDERING,
|
||||
|
||||
/** order by attribute ascending */
|
||||
ATTR_ASC,
|
||||
|
||||
/** order by attribute descending */
|
||||
ATTR_DESC,
|
||||
|
||||
/** order by count ascending and attribute ascending */
|
||||
COUNT_ASC_ATTR_ASC,
|
||||
|
||||
/** order by count ascending and attribute descending */
|
||||
COUNT_ASC_ATTR_DESC,
|
||||
|
||||
/** order by count descending and attribute ascending */
|
||||
COUNT_DESC_ATTR_ASC,
|
||||
|
||||
/** order by count descending and attribute descending */
|
||||
COUNT_DESC_ATTR_DESC,
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.io.Serializable;
|
||||
/**
|
||||
* Holds a distinct value with it's count.
|
||||
* (Used with {@link Query#findSingleAttributeList()} and {@link Query#setCountDistinct(CountDistinctOrder)}.)
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*/
|
||||
public class CountedValue<A> implements Serializable {
|
||||
private static final long serialVersionUID = -2267971668356749695L;
|
||||
|
||||
private final A value;
|
||||
private final long count;
|
||||
|
||||
public CountedValue(A value, long count) {
|
||||
this.value = value;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public long getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public A getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return count + ": " + value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package io.ebean;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Query for performing native SQL queries that return DTO Bean's.
|
||||
* <p>
|
||||
* These beans are just normal classes. They must have public constructors
|
||||
* and setters.
|
||||
* <p>
|
||||
* Constructors with arguments are used if the number of constructor arguments
|
||||
* matches the number of columns in the resultSet.
|
||||
* </p>
|
||||
* <p>
|
||||
* If the number of columns in the resultSet is greater than the largest constructor
|
||||
* then the largest constructor is used for the first columns and remaining columns
|
||||
* are mapped by setter methods.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* // CustomerDto is just a 'bean like' class
|
||||
* // with public constructor(s) and public setter methods
|
||||
*
|
||||
* String sql = "select id, name from customer where name like :name and status_code = :status";
|
||||
*
|
||||
* List<CustomerDto> beans =
|
||||
* Ebean.findDto(CustomrDto.class, sql)
|
||||
* .setParameter("name", "Acme%")
|
||||
* .setParameter("status", "ACTIVE")
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public interface DtoQuery<T> {
|
||||
|
||||
/**
|
||||
* Execute the query returning a list.
|
||||
*/
|
||||
@Nonnull
|
||||
List<T> findList();
|
||||
|
||||
/**
|
||||
* Execute the query iterating a row at a time.
|
||||
* <p>
|
||||
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
|
||||
* </p>
|
||||
*/
|
||||
void findEach(Consumer<T> consumer);
|
||||
|
||||
/**
|
||||
* Execute the query iterating a row at a time with the ability to stop consuming part way through.
|
||||
* <p>
|
||||
* Returning false after processing a row stops the iteration through the query results.
|
||||
* </p>
|
||||
* <p>
|
||||
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
|
||||
* </p>
|
||||
*/
|
||||
void findEachWhile(Predicate<T> consumer);
|
||||
|
||||
/**
|
||||
* Execute the query returning a single bean.
|
||||
*/
|
||||
@Nullable
|
||||
T findOne();
|
||||
|
||||
/**
|
||||
* Execute the query returning an optional bean.
|
||||
*/
|
||||
@Nonnull
|
||||
Optional<T> findOneOrEmpty();
|
||||
|
||||
/**
|
||||
* The same as bind for named parameters.
|
||||
*/
|
||||
DtoQuery<T> setParameter(String name, Object value);
|
||||
|
||||
/**
|
||||
* The same as bind for positioned parameters.
|
||||
*/
|
||||
DtoQuery<T> setParameter(int position, Object value);
|
||||
|
||||
/**
|
||||
* Set the index of the first row of the results to return.
|
||||
*/
|
||||
DtoQuery<T> setFirstRow(int firstRow);
|
||||
|
||||
/**
|
||||
* Set the maximum number of query results to return.
|
||||
*/
|
||||
DtoQuery<T> setMaxRows(int maxRows);
|
||||
|
||||
/**
|
||||
* When resultSet columns are not able to be mapped to a bean property then instead of
|
||||
* throwing effectively skip reading that column.
|
||||
*/
|
||||
DtoQuery<T> setRelaxedMode();
|
||||
|
||||
/**
|
||||
* Set a label on the query to make it easier to identify queries related to query execution statistics.
|
||||
*
|
||||
* @param label A label that is unique to the DTO bean type.
|
||||
*/
|
||||
DtoQuery<T> setLabel(String label);
|
||||
|
||||
/**
|
||||
* Set a timeout on this query.
|
||||
* <p>
|
||||
* This will typically result in a call to setQueryTimeout() on a
|
||||
* preparedStatement. If the timeout occurs an exception will be thrown - this
|
||||
* will be a SQLException wrapped up in a PersistenceException.
|
||||
* </p>
|
||||
*
|
||||
* @param secs the query timeout limit in seconds. Zero means there is no limit.
|
||||
*/
|
||||
DtoQuery<T> setTimeout(int secs);
|
||||
|
||||
/**
|
||||
* A hint which for JDBC translates to the Statement.fetchSize().
|
||||
* <p>
|
||||
* Gives the JDBC driver a hint as to the number of rows that should be
|
||||
* fetched from the database when more rows are needed for ResultSet.
|
||||
* </p>
|
||||
*/
|
||||
DtoQuery<T> setBufferFetchSizeHint(int bufferFetchSizeHint);
|
||||
|
||||
}
|
||||
@@ -3,18 +3,21 @@ package io.ebean;
|
||||
import io.ebean.annotation.TxIsolation;
|
||||
import io.ebean.cache.ServerCacheManager;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.plugin.Property;
|
||||
import io.ebean.text.csv.CsvReader;
|
||||
import io.ebean.text.json.JsonContext;
|
||||
import javax.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.OptimisticLockException;
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@@ -114,6 +117,10 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
public final class Ebean {
|
||||
private static final Logger logger = LoggerFactory.getLogger(Ebean.class);
|
||||
|
||||
static {
|
||||
EbeanVersion.getVersion(); // initalizes the version class and logs the version.
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages creation and cache of EbeanServers.
|
||||
*/
|
||||
@@ -131,8 +138,7 @@ public final class Ebean {
|
||||
private final ConcurrentHashMap<String, EbeanServer> concMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Cache for synchronized read, creation and put. Protected by the monitor
|
||||
* object.
|
||||
* Cache for synchronized read, creation and put. Protected by the monitor object.
|
||||
*/
|
||||
private final HashMap<String, EbeanServer> syncMap = new HashMap<>();
|
||||
|
||||
@@ -146,14 +152,7 @@ public final class Ebean {
|
||||
private ServerManager() {
|
||||
|
||||
try {
|
||||
// skipDefaultServer is set by EbeanServerFactory
|
||||
// ... when it is creating the primaryServer
|
||||
if (PrimaryServer.isSkip()) {
|
||||
// primary server being created by EbeanServerFactory
|
||||
// ... so we should not try and create it here
|
||||
logger.debug("PrimaryServer.isSkip()");
|
||||
|
||||
} else {
|
||||
if (!PrimaryServer.isSkip()) {
|
||||
// look to see if there is a default server defined
|
||||
String defaultName = PrimaryServer.getDefaultServerName();
|
||||
logger.debug("defaultName:{}", defaultName);
|
||||
@@ -376,7 +375,7 @@ public final class Ebean {
|
||||
* Note that this provides an try finally alternative to using {@link #executeCall(TxScope, Callable)} or
|
||||
* {@link #execute(TxScope, Runnable)}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <h3>REQUIRES_NEW example:</h3>
|
||||
* <pre>{@code
|
||||
* // Start a new transaction. If there is a current transaction
|
||||
@@ -646,6 +645,65 @@ public final class Ebean {
|
||||
return serverMgr.getDefaultServer().saveAll(beans);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks the uniqueness of a bean. I.e. if the save will work. It will return the
|
||||
* properties that violates an unique / primary key. This may be done in an UI save action to
|
||||
* validate if the user has entered correct values.
|
||||
* <p>
|
||||
* Note: This method queries the DB for uniqueness of all indices, so do not use it in a batch update.
|
||||
* <p>
|
||||
* Note: This checks only the root bean!
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // there is a unique constraint on title
|
||||
*
|
||||
* Document doc = new Document();
|
||||
* doc.setTitle("One flew over the cuckoo's nest");
|
||||
* doc.setBody("clashes with doc1");
|
||||
*
|
||||
* Set<Property> properties = server().checkUniqueness(doc);
|
||||
*
|
||||
* if (properties.isEmpty()) {
|
||||
* // it is unique ... carry on
|
||||
*
|
||||
* } else {
|
||||
* // build a user friendly message
|
||||
* // to return message back to user
|
||||
*
|
||||
* String uniqueProperties = properties.toString();
|
||||
*
|
||||
* StringBuilder msg = new StringBuilder();
|
||||
*
|
||||
* properties.forEach((it)-> {
|
||||
* Object propertyValue = it.getVal(doc);
|
||||
* String propertyName = it.getName();
|
||||
* msg.append(" property["+propertyName+"] value["+propertyValue+"]");
|
||||
* });
|
||||
*
|
||||
* // uniqueProperties > [title]
|
||||
* // custom msg > property[title] value[One flew over the cuckoo's nest]
|
||||
*
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param bean The entity bean to check uniqueness on
|
||||
* @return a set of Properties if constraint validation was detected or empty list.
|
||||
*/
|
||||
@Nonnull
|
||||
public static Set<Property> checkUniqueness(Object bean) {
|
||||
return serverMgr.getDefaultServer().checkUniqueness(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #checkUniqueness(Object)}. but with given transaction.
|
||||
*/
|
||||
@Nonnull
|
||||
public static Set<Property> checkUniqueness(Object bean, Transaction transaction) {
|
||||
return serverMgr.getDefaultServer().checkUniqueness(bean, transaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the bean.
|
||||
* <p>
|
||||
@@ -1058,6 +1116,21 @@ public final class Ebean {
|
||||
return serverMgr.getDefaultServer().findNative(beanType, nativeSql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Query for DTO beans.
|
||||
* <p>
|
||||
* DTO beans are just normal bean like classes with public constructor(s) and setters.
|
||||
* They do not need to be registered with Ebean before use.
|
||||
* </p>
|
||||
*
|
||||
* @param dtoType The type of the DTO bean the rows will be mapped into.
|
||||
* @param sql The SQL query to execute.
|
||||
* @param <T> The type of the DTO bean.
|
||||
*/
|
||||
public static <T> DtoQuery<T> findDto(Class<T> dtoType, String sql) {
|
||||
return serverMgr.getDefaultServer().findDto(dtoType, sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an Update query to perform a bulk update.
|
||||
* <p>
|
||||
|
||||
@@ -4,13 +4,13 @@ import io.ebean.annotation.TxIsolation;
|
||||
import io.ebean.cache.ServerCacheManager;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.meta.MetaInfoManager;
|
||||
import io.ebean.plugin.Property;
|
||||
import io.ebean.plugin.SpiServer;
|
||||
import io.ebean.text.csv.CsvReader;
|
||||
import io.ebean.text.json.JsonContext;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import javax.persistence.NonUniqueResultException;
|
||||
import javax.persistence.OptimisticLockException;
|
||||
import javax.persistence.PersistenceException;
|
||||
@@ -424,6 +424,19 @@ public interface EbeanServer {
|
||||
*/
|
||||
<T> Update<T> createUpdate(Class<T> beanType, String ormUpdate);
|
||||
|
||||
/**
|
||||
* Create a Query for DTO beans.
|
||||
* <p>
|
||||
* DTO beans are just normal bean like classes with public constructor(s) and setters.
|
||||
* They do not need to be registered with Ebean before use.
|
||||
* </p>
|
||||
*
|
||||
* @param dtoType The type of the DTO bean the rows will be mapped into.
|
||||
* @param sql The SQL query to execute.
|
||||
* @param <T> The type of the DTO bean.
|
||||
*/
|
||||
<T> DtoQuery<T> findDto(Class<T> dtoType, String sql);
|
||||
|
||||
/**
|
||||
* Create a SqlQuery for executing native sql
|
||||
* query statements.
|
||||
@@ -1461,6 +1474,61 @@ public interface EbeanServer {
|
||||
*/
|
||||
int saveAll(Collection<?> beans, Transaction transaction) throws OptimisticLockException;
|
||||
|
||||
/**
|
||||
* This method checks the uniqueness of a bean. I.e. if the save will work. It will return the
|
||||
* properties that violates an unique / primary key. This may be done in an UI save action to
|
||||
* validate if the user has entered correct values.
|
||||
* <p>
|
||||
* Note: This method queries the DB for uniqueness of all indices, so do not use it in a batch update.
|
||||
* <p>
|
||||
* Note: This checks only the root bean!
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // there is a unique constraint on title
|
||||
*
|
||||
* Document doc = new Document();
|
||||
* doc.setTitle("One flew over the cuckoo's nest");
|
||||
* doc.setBody("clashes with doc1");
|
||||
*
|
||||
* Set<Property> properties = server().checkUniqueness(doc);
|
||||
*
|
||||
* if (properties.isEmpty()) {
|
||||
* // it is unique ... carry on
|
||||
*
|
||||
* } else {
|
||||
* // build a user friendly message
|
||||
* // to return message back to user
|
||||
*
|
||||
* String uniqueProperties = properties.toString();
|
||||
*
|
||||
* StringBuilder msg = new StringBuilder();
|
||||
*
|
||||
* properties.forEach((it)-> {
|
||||
* Object propertyValue = it.getVal(doc);
|
||||
* String propertyName = it.getName();
|
||||
* msg.append(" property["+propertyName+"] value["+propertyValue+"]");
|
||||
* });
|
||||
*
|
||||
* // uniqueProperties > [title]
|
||||
* // custom msg > property[title] value[One flew over the cuckoo's nest]
|
||||
*
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param bean The entity bean to check uniqueness on
|
||||
* @return a set of Properties if constraint validation was detected or empty list.
|
||||
*/
|
||||
@Nonnull
|
||||
Set<Property> checkUniqueness(Object bean);
|
||||
|
||||
/**
|
||||
* Same as {@link #checkUniqueness(Object)}. but with given transaction.
|
||||
*/
|
||||
@Nonnull
|
||||
Set<Property> checkUniqueness(Object bean, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Marks the entity bean as dirty.
|
||||
* <p>
|
||||
|
||||
@@ -33,6 +33,10 @@ public class EbeanServerFactory {
|
||||
|
||||
private static SpiContainer container;
|
||||
|
||||
static {
|
||||
EbeanVersion.getVersion(); // initalizes the version class and logs the version.
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the container with clustering configuration.
|
||||
* <p>
|
||||
@@ -65,10 +69,8 @@ public class EbeanServerFactory {
|
||||
|
||||
EbeanServer server = createInternal(config);
|
||||
|
||||
if (config.isDefaultServer()) {
|
||||
PrimaryServer.setSkip(true);
|
||||
}
|
||||
if (config.isRegister()) {
|
||||
PrimaryServer.setSkip(true);
|
||||
Ebean.register(server, config.isDefaultServer());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
/**
|
||||
* Class to determine the ebean version. (
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*
|
||||
*/
|
||||
public class EbeanVersion {
|
||||
private EbeanVersion() {
|
||||
|
||||
}
|
||||
private static final Logger logger = LoggerFactory.getLogger(EbeanVersion.class);
|
||||
|
||||
private static String version = "unknown";
|
||||
static {
|
||||
try {
|
||||
Properties prop = new Properties();
|
||||
InputStream in = Ebean.class.getResourceAsStream("/META-INF/maven/io.ebean/ebean/pom.properties");
|
||||
if (in != null) {
|
||||
prop.load(in);
|
||||
in.close();
|
||||
version = prop.getProperty("version");
|
||||
}
|
||||
logger.info("ebean version: {}", version);
|
||||
} catch (IOException e) {
|
||||
logger.warn("Could not determine ebean version: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ebean version (read from /META-INF/maven/io.ebean/ebean/pom.properties)
|
||||
*/
|
||||
public static String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -480,6 +480,36 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
Query<T> setUseQueryCache(CacheMode useCache);
|
||||
|
||||
/**
|
||||
* Extended version for setDistinct in conjunction with "findSingleAttributeList";
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<CountedValue<Order.Status>> orderStatusCount =
|
||||
*
|
||||
* Ebean.find(Order.class)
|
||||
* .select("status")
|
||||
* .where()
|
||||
* .gt("orderDate", LocalDate.now().minusMonths(3))
|
||||
*
|
||||
* // fetch as single attribute with a COUNT
|
||||
* .setCountDistinct(CountDistinctOrder.COUNT_DESC_ATTR_ASC)
|
||||
* .findSingleAttributeList();
|
||||
*
|
||||
* for (CountedValue<Order.Status> entry : orderStatusCount) {
|
||||
* System.out.println(" count:" + entry.getCount()+" orderStatus:" + entry.getValue() );
|
||||
* }
|
||||
*
|
||||
* // produces
|
||||
*
|
||||
* count:3 orderStatus:NEW
|
||||
* count:1 orderStatus:SHIPPED
|
||||
* count:1 orderStatus:COMPLETE
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
Query<T> setCountDistinct(CountDistinctOrder orderBy);
|
||||
|
||||
/**
|
||||
* Calls {@link #setUseQueryCache(CacheMode)} with <code>ON</code> or <code>OFF</code>.
|
||||
*
|
||||
|
||||
@@ -158,6 +158,7 @@ public class Pairs {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "p0:" + property0 + " p1:" + property1 + " entries:" + entries;
|
||||
}
|
||||
@@ -183,6 +184,7 @@ public class Pairs {
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{" + a + "," + b + "}";
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.ebean;
|
||||
|
||||
import io.ebean.config.PropertyMap;
|
||||
import io.ebean.config.properties.PropertiesLoader;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
@@ -44,7 +44,7 @@ class PrimaryServer {
|
||||
*/
|
||||
static synchronized Properties getProperties() {
|
||||
if (globalProperties == null) {
|
||||
globalProperties = PropertyMap.defaultProperties();
|
||||
globalProperties = PropertiesLoader.load();
|
||||
}
|
||||
if (defaultServerName == null) {
|
||||
defaultServerName = determineDefaultServerName();
|
||||
|
||||
@@ -236,6 +236,14 @@ public interface Query<T> {
|
||||
*/
|
||||
Query<T> asDraft();
|
||||
|
||||
/**
|
||||
* Convert the query to a DTO bean query.
|
||||
* <p>
|
||||
* We effectively use the underlying ORM query to build the SQL and then execute
|
||||
* and map it into DTO beans.
|
||||
*/
|
||||
<D> DtoQuery<D> asDto(Class<D> dtoClass);
|
||||
|
||||
/**
|
||||
* Cancel the query execution if supported by the underlying database and
|
||||
* driver.
|
||||
@@ -828,6 +836,11 @@ public interface Query<T> {
|
||||
*/
|
||||
<A> A findSingleAttribute();
|
||||
|
||||
/**
|
||||
* Return true if this is countDistinct query.
|
||||
*/
|
||||
boolean isCountDistinct();
|
||||
|
||||
/**
|
||||
* Execute the query returning either a single bean or null (if no matching
|
||||
* bean is found).
|
||||
@@ -1272,6 +1285,37 @@ public interface Query<T> {
|
||||
*/
|
||||
Query<T> setDistinct(boolean isDistinct);
|
||||
|
||||
/**
|
||||
* Extended version for setDistinct in conjunction with "findSingleAttributeList";
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<CountedValue<Order.Status>> orderStatusCount =
|
||||
*
|
||||
* Ebean.find(Order.class)
|
||||
* .select("status")
|
||||
* .where()
|
||||
* .gt("orderDate", LocalDate.now().minusMonths(3))
|
||||
*
|
||||
* // fetch as single attribute with a COUNT
|
||||
* .setCountDistinct(CountDistinctOrder.COUNT_DESC_ATTR_ASC)
|
||||
* .findSingleAttributeList();
|
||||
*
|
||||
* for (CountedValue<Order.Status> entry : orderStatusCount) {
|
||||
* System.out.println(" count:" + entry.getCount()+" orderStatus:" + entry.getValue() );
|
||||
* }
|
||||
*
|
||||
* // produces
|
||||
*
|
||||
* count:3 orderStatus:NEW
|
||||
* count:1 orderStatus:SHIPPED
|
||||
* count:1 orderStatus:COMPLETE
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
*/
|
||||
Query<T> setCountDistinct(CountDistinctOrder orderBy);
|
||||
|
||||
/**
|
||||
* Return the first row value.
|
||||
*/
|
||||
@@ -1436,9 +1480,12 @@ public interface Query<T> {
|
||||
String getGeneratedSql();
|
||||
|
||||
/**
|
||||
* Deprecated - migrate to forUpdate().
|
||||
*
|
||||
* executed the select with "for update" which should lock the record
|
||||
* "on read"
|
||||
*/
|
||||
@Deprecated
|
||||
Query<T> setForUpdate(boolean forUpdate);
|
||||
|
||||
/**
|
||||
@@ -1482,6 +1529,11 @@ public interface Query<T> {
|
||||
*/
|
||||
Class<T> getBeanType();
|
||||
|
||||
/**
|
||||
* Return the type of query being executed.
|
||||
*/
|
||||
QueryType getQueryType();
|
||||
|
||||
/**
|
||||
* Set true if you want to disable lazy loading.
|
||||
* <p>
|
||||
@@ -1499,4 +1551,12 @@ public interface Query<T> {
|
||||
*/
|
||||
Set<String> validate();
|
||||
|
||||
/**
|
||||
* Controls, if paginated queries should always append an 'order by id' statement at the end to
|
||||
* guarantee a deterministic sort result. This may affect performance.
|
||||
* If this is not enabled, and an orderBy is set on the query, it's up to the programmer that
|
||||
* this query provides a deterministic result.
|
||||
*/
|
||||
Query<T> orderById(boolean orderById);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package io.ebean;
|
||||
|
||||
/**
|
||||
* The type of the query being executed.
|
||||
*/
|
||||
public enum QueryType {
|
||||
|
||||
/**
|
||||
* A find query.
|
||||
*/
|
||||
FIND,
|
||||
|
||||
/**
|
||||
* An update query.
|
||||
*/
|
||||
UPDATE,
|
||||
|
||||
/**
|
||||
* A delete query.
|
||||
*/
|
||||
DELETE
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Builds RawSql instances from a SQL string and column mappings.
|
||||
@@ -24,6 +25,13 @@ public interface RawSqlBuilder {
|
||||
return XServiceProvider.rawSql().resultSet(resultSet, propertyNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a SqlRow based on the resultSet with dbTrueValue and binaryOptimizedUUID options.
|
||||
*/
|
||||
static SqlRow sqlRow(ResultSet resultSet, final String dbTrueValue, boolean binaryOptimizedUUID) throws SQLException {
|
||||
return XServiceProvider.rawSql().sqlRow(resultSet, dbTrueValue, binaryOptimizedUUID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an unparsed RawSqlBuilder. Unlike a parsed one this query can not be
|
||||
* modified - so no additional WHERE or HAVING expressions can be added to
|
||||
|
||||
@@ -54,6 +54,25 @@ public interface SqlUpdate {
|
||||
*/
|
||||
int execute();
|
||||
|
||||
/**
|
||||
* Return the generated key value.
|
||||
*/
|
||||
Object getGeneratedKey();
|
||||
|
||||
/**
|
||||
* Execute and return the generated key. This is effectively a short cut for:
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* sqlUpdate.execute();
|
||||
* Object key = sqlUpdate.getGeneratedKey();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return The generated key value
|
||||
*/
|
||||
Object executeGetKey();
|
||||
|
||||
/**
|
||||
* Return true if eBean should automatically deduce the table modification
|
||||
* information and process it.
|
||||
@@ -88,6 +107,11 @@ public interface SqlUpdate {
|
||||
*/
|
||||
SqlUpdate setLabel(String label);
|
||||
|
||||
/**
|
||||
* Set to true when we want to use getGeneratedKeys with this statement.
|
||||
*/
|
||||
SqlUpdate setGetGeneratedKeys(boolean getGeneratedKeys);
|
||||
|
||||
/**
|
||||
* Return the sql statement.
|
||||
*/
|
||||
|
||||
@@ -191,6 +191,11 @@ public interface BeanCollection<E> extends Serializable {
|
||||
*/
|
||||
void setModifyListening(ModifyListenMode modifyListenMode);
|
||||
|
||||
/**
|
||||
* Return the current modify listening mode. Can be null for on newly created beans.
|
||||
*/
|
||||
ModifyListenMode getModifyListening();
|
||||
|
||||
/**
|
||||
* Add an object to the additions list.
|
||||
* <p>
|
||||
@@ -227,6 +232,11 @@ public interface BeanCollection<E> extends Serializable {
|
||||
*/
|
||||
void modifyReset();
|
||||
|
||||
/**
|
||||
* Has been modified by an addition or removal.
|
||||
*/
|
||||
boolean wasTouched();
|
||||
|
||||
/**
|
||||
* Return a shallow copy of this collection that is modifiable.
|
||||
*/
|
||||
|
||||
@@ -43,6 +43,7 @@ public final class CallStack implements Serializable {
|
||||
return hc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hc;
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
private int lazyLoadProperty = -1;
|
||||
|
||||
private Object ownerId;
|
||||
private int sortOrder;
|
||||
|
||||
/**
|
||||
* Create a intercept with a given entity.
|
||||
@@ -528,6 +529,16 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set old value but force it to be set regardless if it already has a value.
|
||||
*/
|
||||
private void setOriginalValueForce(int propertyIndex, Object value) {
|
||||
if (origValues == null) {
|
||||
origValues = new Object[owner._ebean_getPropertyNames().length];
|
||||
}
|
||||
origValues[propertyIndex] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* For forced update on a 'New' bean set all the loaded properties to changed.
|
||||
*/
|
||||
@@ -693,6 +704,9 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
* Add and return a dirty property hash recursing into embedded beans.
|
||||
*/
|
||||
private void addDirtyPropertyKey(StringBuilder sb) {
|
||||
if (sortOrder > 0) {
|
||||
sb.append("s,");
|
||||
}
|
||||
int len = getPropertyLength();
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (changedProps != null && changedProps[i]) {
|
||||
@@ -897,15 +911,19 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
|
||||
if (setDirtyState) {
|
||||
setOriginalValue(propertyIndex, origValue);
|
||||
if (!dirty) {
|
||||
dirty = true;
|
||||
if (embeddedOwner != null) {
|
||||
// Cascade dirty state from Embedded bean to parent bean
|
||||
embeddedOwner._ebean_getIntercept().setEmbeddedDirty(embeddedOwnerIndex);
|
||||
}
|
||||
if (nodeUsageCollector != null) {
|
||||
nodeUsageCollector.setModified();
|
||||
}
|
||||
setDirtyStatus();
|
||||
}
|
||||
}
|
||||
|
||||
private void setDirtyStatus() {
|
||||
if (!dirty) {
|
||||
dirty = true;
|
||||
if (embeddedOwner != null) {
|
||||
// Cascade dirty state from Embedded bean to parent bean
|
||||
embeddedOwner._ebean_getIntercept().setEmbeddedDirty(embeddedOwnerIndex);
|
||||
}
|
||||
if (nodeUsageCollector != null) {
|
||||
nodeUsageCollector.setModified();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1045,9 +1063,25 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly set an old value.
|
||||
* Explicitly set an old value with force (the old value is forced even it is already set).
|
||||
*/
|
||||
public void setOldValue(int propertyIndex, Object oldValue) {
|
||||
setChangedPropertyValue(propertyIndex, true, oldValue);
|
||||
setChangedProperty(propertyIndex);
|
||||
setOriginalValueForce(propertyIndex, oldValue);
|
||||
setDirtyStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sort order value for an order column.
|
||||
*/
|
||||
public int getSortOrder() {
|
||||
return sortOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sort order value for an order column.
|
||||
*/
|
||||
public void setSortOrder(int sortOrder) {
|
||||
this.sortOrder = sortOrder;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +140,11 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
|
||||
// Support for modify additions deletions etc - ManyToMany
|
||||
// ---------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public ModifyListenMode getModifyListening() {
|
||||
return modifyListenMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* set modifyListening to be on or off.
|
||||
*/
|
||||
@@ -213,7 +218,12 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
|
||||
boolean holdsModifications() {
|
||||
return modifyHolder != null && modifyHolder.hasModifications();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean wasTouched() {
|
||||
return modifyHolder != null && modifyHolder.wasTouched();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies all relevant properties for a clone. See {@link #getShallowCopy()}
|
||||
* @param other
|
||||
|
||||
@@ -28,7 +28,10 @@ class ModifyHolder<E> implements Serializable {
|
||||
*/
|
||||
private Set<E> modifyAdditions = new LinkedHashSet<>();
|
||||
|
||||
private boolean touched;
|
||||
|
||||
void reset() {
|
||||
touched = false;
|
||||
modifyDeletions = new LinkedHashSet<>();
|
||||
modifyAdditions = new LinkedHashSet<>();
|
||||
}
|
||||
@@ -50,6 +53,7 @@ class ModifyHolder<E> implements Serializable {
|
||||
|
||||
void modifyAddition(E bean) {
|
||||
if (bean != null) {
|
||||
touched = true;
|
||||
// If it is to delete then just remove the deletion
|
||||
if (!undoDeletion(bean)) {
|
||||
// Insert
|
||||
@@ -65,6 +69,7 @@ class ModifyHolder<E> implements Serializable {
|
||||
@SuppressWarnings("unchecked")
|
||||
void modifyRemoval(Object bean) {
|
||||
if (bean != null) {
|
||||
touched = true;
|
||||
// If it is to be added then just remove the addition
|
||||
if (!undoAddition(bean)) {
|
||||
modifyDeletions.add((E) bean);
|
||||
@@ -80,6 +85,14 @@ class ModifyHolder<E> implements Serializable {
|
||||
return modifyDeletions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the collection was touched in some way. This is still true even if
|
||||
* a bean was removed and then added (which is not held as a modification).
|
||||
*/
|
||||
boolean wasTouched() {
|
||||
return touched;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there additions or removals.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.ebean.config;
|
||||
|
||||
/**
|
||||
* Used to provide some automatic configuration early in the creation of an EbeanServer.
|
||||
*/
|
||||
public interface AutoConfigure {
|
||||
|
||||
/**
|
||||
* Provide some configuration the ServerConfig prior to server creation.
|
||||
* <p>
|
||||
* Return true if the autoConfiguration applies to this ServerConfig.
|
||||
*/
|
||||
void configure(ServerConfig serverConfig);
|
||||
|
||||
}
|
||||
@@ -1,11 +1,16 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import io.ebean.EbeanVersion;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.migration.MigrationConfig;
|
||||
import io.ebean.migration.MigrationRunner;
|
||||
import io.ebean.util.StringHelper;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
@@ -116,6 +121,11 @@ public class DbMigrationConfig {
|
||||
*/
|
||||
protected boolean strictMode = true;
|
||||
|
||||
/**
|
||||
* Contains the DDL-header information.
|
||||
*/
|
||||
protected String ddlHeader;
|
||||
|
||||
/**
|
||||
* Return the DB platform to generate migration DDL for.
|
||||
* <p>
|
||||
@@ -387,6 +397,14 @@ public class DbMigrationConfig {
|
||||
return patchResetChecksumOn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a DDL header prepend for each DDL. E.g. for copyright headers
|
||||
* You can use placeholders like ${version} or ${timestamp} in properties file.
|
||||
*/
|
||||
public String getDdlHeader() {
|
||||
return ddlHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set migration versions that should have their checksum reset and not run.
|
||||
* <p>
|
||||
@@ -447,13 +465,20 @@ public class DbMigrationConfig {
|
||||
metaTable = properties.get("migration.metaTable", metaTable);
|
||||
runPlaceholders = properties.get("migration.placeholders", runPlaceholders);
|
||||
|
||||
String adminUser = properties.get("datasource." + serverName + ".username", dbUsername);
|
||||
adminUser = properties.get("datasource." + serverName + ".adminusername", adminUser);
|
||||
//Do not set user and pass from "datasource.db.username"
|
||||
//There is a null test in MigrationRunner::getConnection to handle this
|
||||
//String adminUser = properties.get("datasource." + serverName + ".username", dbUsername);
|
||||
String adminUser = properties.get("datasource." + serverName + ".adminusername", dbUsername);
|
||||
dbUsername = properties.get("migration.dbusername", adminUser);
|
||||
|
||||
String adminPwd = properties.get("datasource." + serverName + ".password", dbPassword);
|
||||
adminPwd = properties.get("datasource." + serverName + ".adminpassword", adminPwd);
|
||||
//String adminPwd = properties.get("datasource." + serverName + ".password", dbPassword);
|
||||
String adminPwd = properties.get("datasource." + serverName + ".adminpassword", dbPassword);
|
||||
dbPassword = properties.get("migration.dbpassword", adminPwd);
|
||||
ddlHeader = properties.get("ddl.header", ddlHeader);
|
||||
if (ddlHeader != null && !ddlHeader.isEmpty()) {
|
||||
ddlHeader = StringHelper.replaceString(ddlHeader, "${version}", EbeanVersion.getVersion());
|
||||
ddlHeader = StringHelper.replaceString(ddlHeader, "${timestamp}", ZonedDateTime.now().format( DateTimeFormatter.ISO_INSTANT ));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,16 +10,13 @@ public class PropertiesWrapper {
|
||||
|
||||
protected final String serverName;
|
||||
|
||||
protected final PropertyMap propertyMap;
|
||||
|
||||
/**
|
||||
* Construct with a prefix, serverName and properties.
|
||||
*/
|
||||
public PropertiesWrapper(String prefix, String serverName, Properties properties) {
|
||||
this.serverName = serverName;
|
||||
this.prefix = prefix;
|
||||
this.propertyMap = PropertyMapLoader.load(null, properties);
|
||||
this.properties = propertyMap.asProperties();
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -29,27 +26,6 @@ public class PropertiesWrapper {
|
||||
this(null, null, properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal copy constructor when changing prefix.
|
||||
*/
|
||||
protected PropertiesWrapper(String prefix, String serverName, PropertyMap propertyMap, Properties properties) {
|
||||
this.serverName = serverName;
|
||||
this.prefix = prefix;
|
||||
this.propertyMap = propertyMap;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a PropertiesWrapper instance with a different prefix but same underlying properties.
|
||||
* <p/>
|
||||
* Used when wanting to use "datasource" as the prefix rather than "ebean".
|
||||
* <p/>
|
||||
* The returning instance should only be used in a read only fashion.
|
||||
*/
|
||||
public PropertiesWrapper withPrefix(String prefix) {
|
||||
return new PropertiesWrapper(prefix, serverName, propertyMap, properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the serverName (optional).
|
||||
*/
|
||||
@@ -57,16 +33,6 @@ public class PropertiesWrapper {
|
||||
return serverName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return as Properties with lower case keys and after evaluation and additional properties loading has occurred.
|
||||
* <p>
|
||||
* Ebean has historically ignored the case of keys hence returning the Properties with all the keys lower cased.
|
||||
* </p>
|
||||
*/
|
||||
public Properties asPropertiesLowerCase() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a property with no default value.
|
||||
*/
|
||||
@@ -90,17 +56,21 @@ public class PropertiesWrapper {
|
||||
|
||||
String value = null;
|
||||
if (serverName != null && prefix != null) {
|
||||
value = propertyMap.get(prefix + "." + serverName + "." + key, null);
|
||||
value = internalGet(prefix + "." + serverName + "." + key);
|
||||
}
|
||||
if (value == null && prefix != null) {
|
||||
value = propertyMap.get(prefix + "." + key, null);
|
||||
value = internalGet(prefix + "." + key);
|
||||
}
|
||||
if (value == null) {
|
||||
value = propertyMap.get(key, null);
|
||||
value = internalGet(key);
|
||||
}
|
||||
return value == null ? defaultValue : value;
|
||||
}
|
||||
|
||||
private String internalGet(String key) {
|
||||
return properties.getProperty(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a double property value.
|
||||
*/
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.naming.InitialContext;
|
||||
import javax.naming.NamingException;
|
||||
|
||||
/**
|
||||
* Helper used to evaluate expressions such as ${CATALINA_HOME}.
|
||||
* <p>
|
||||
* The expressions can contain environment variables, system properties or JNDI
|
||||
* properties. JNDI expressions take the form ${jndi:propertyName} where you
|
||||
* substitute propertyName with the name of the jndi property you wish to
|
||||
* evaluate.
|
||||
* </p>
|
||||
*/
|
||||
final class PropertyExpression {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PropertyExpression.class);
|
||||
|
||||
/**
|
||||
* Prefix for looking up JNDI Environment variable.
|
||||
*/
|
||||
private static final String JAVA_COMP_ENV = "java:comp/env/";
|
||||
|
||||
/**
|
||||
* Used to detect the start of an expression.
|
||||
*/
|
||||
private static final String START = "${";
|
||||
|
||||
/**
|
||||
* Used to detect the end of an expression.
|
||||
*/
|
||||
private static final String END = "}";
|
||||
|
||||
/**
|
||||
* Specify the PropertyHolder.
|
||||
*/
|
||||
private PropertyExpression() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property value evaluating and replacing any expressions such as
|
||||
* ${CATALINA_HOME}.
|
||||
*/
|
||||
static String eval(String val, PropertyMap map) {
|
||||
if (val == null) {
|
||||
return null;
|
||||
}
|
||||
int sp = val.indexOf(START);
|
||||
if (sp > -1) {
|
||||
int ep = val.indexOf(END, sp + 1);
|
||||
if (ep > -1) {
|
||||
return eval(val, sp, ep, map);
|
||||
}
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the expression using JNDI, Environment variables, System Properties
|
||||
* or existing an property in SystemProperties itself.
|
||||
*/
|
||||
private static String evaluateExpression(String exp, PropertyMap map) {
|
||||
|
||||
if (isJndiExpression(exp)) {
|
||||
// JNDI property lookup...
|
||||
String val = getJndiProperty(exp);
|
||||
if (val != null) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
// check Environment Variables first
|
||||
String val = System.getenv(exp);
|
||||
if (val == null) {
|
||||
// then check system properties
|
||||
val = System.getProperty(exp);
|
||||
}
|
||||
if (val == null && map != null) {
|
||||
// then check PropertyMap
|
||||
val = map.get(exp);
|
||||
}
|
||||
|
||||
if (val != null) {
|
||||
return val;
|
||||
|
||||
} else {
|
||||
// unable to evaluate yet... but maybe later based on the order
|
||||
// in which properties are being set/loaded. You can use
|
||||
// GlobalProperties.evaluateExpressions() to get any unresolved
|
||||
// expressions to be evaluated
|
||||
logger.debug("Unable to evaluate expression [{}]", exp);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String eval(String val, int sp, int ep, PropertyMap map) {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(val.substring(0, sp));
|
||||
|
||||
String cal = evalExpression(val, sp, ep, map);
|
||||
sb.append(cal);
|
||||
|
||||
eval(val, ep + 1, sb, map);
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void eval(String val, int startPos, StringBuilder sb, PropertyMap map) {
|
||||
if (startPos < val.length()) {
|
||||
int sp = val.indexOf(START, startPos);
|
||||
if (sp > -1) {
|
||||
// append what is between the last token and the new one (if startPos ==
|
||||
// sp nothing gets added)
|
||||
sb.append(val.substring(startPos, sp));
|
||||
int ep = val.indexOf(END, sp + 1);
|
||||
if (ep > -1) {
|
||||
String cal = evalExpression(val, sp, ep, map);
|
||||
sb.append(cal);
|
||||
eval(val, ep + 1, sb, map);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// append what is left...
|
||||
sb.append(val.substring(startPos));
|
||||
}
|
||||
|
||||
private static String evalExpression(String val, int sp, int ep, PropertyMap map) {
|
||||
// trim off start and end ${ and }
|
||||
String exp = val.substring(sp + START.length(), ep);
|
||||
|
||||
// evaluate the variable
|
||||
String evaled = evaluateExpression(exp, map);
|
||||
if (evaled != null) {
|
||||
return evaled;
|
||||
} else {
|
||||
// unable to evaluate at this stage (maybe later)
|
||||
return START + exp + END;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isJndiExpression(String exp) {
|
||||
return exp.startsWith("JNDI:") || exp.startsWith("jndi:");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null if JNDI is not setup or if the property is not found.
|
||||
*
|
||||
* @param key the key of the JNDI Environment property including a JNDI: prefix.
|
||||
*/
|
||||
private static String getJndiProperty(String key) {
|
||||
|
||||
try {
|
||||
// remove the JNDI: prefix
|
||||
key = key.substring(5);
|
||||
|
||||
return (String) getJndiObject(key);
|
||||
|
||||
} catch (NamingException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to getProperty but throws NamingException if JNDI is not setup or
|
||||
* if the property is not found.
|
||||
*/
|
||||
private static Object getJndiObject(String key) throws NamingException {
|
||||
|
||||
InitialContext ctx = new InitialContext();
|
||||
return ctx.lookup(JAVA_COMP_ENV + key);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A map like structure of properties.
|
||||
* <p/>
|
||||
* Handles evaluation of expressions like ${home} and provides convenience methods for int, long and boolean.
|
||||
*/
|
||||
public final class PropertyMap implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final LinkedHashMap<String, String> map = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* Return properties loaded from test-ebean.properties.
|
||||
*/
|
||||
public static Properties testProperties() {
|
||||
PropertyMap propertyMap = PropertyMapLoader.loadTestProperties();
|
||||
return (propertyMap == null) ? new Properties() : propertyMap.asProperties();
|
||||
}
|
||||
|
||||
public static Properties defaultProperties() {
|
||||
PropertyMap propertyMap = PropertyMapLoader.loadGlobalProperties();
|
||||
return (propertyMap == null) ? new Properties() : propertyMap.asProperties();
|
||||
}
|
||||
|
||||
public static boolean loadTestProperties() {
|
||||
String disable = System.getProperty("disableTestProperties");
|
||||
return (disable == null || !disable.equalsIgnoreCase("true"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return map.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return as standard Properties.
|
||||
*/
|
||||
public Properties asProperties() {
|
||||
Properties properties = new Properties();
|
||||
for (Entry<String, String> e : entrySet()) {
|
||||
properties.put(e.getKey(), e.getValue());
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Go through all the properties and evaluate any expressions that have not
|
||||
* been resolved.
|
||||
*/
|
||||
public void evaluateProperties() {
|
||||
|
||||
for (Entry<String, String> e : entrySet()) {
|
||||
String key = e.getKey();
|
||||
String val = e.getValue();
|
||||
String eval = eval(val);
|
||||
if (eval != null && !eval.equals(val)) {
|
||||
put(key, eval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value with expressions like ${home} evaluated using system properties and environment variables.
|
||||
*/
|
||||
public synchronized String eval(String val) {
|
||||
return PropertyExpression.eval(val, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the boolean property value with a given default.
|
||||
*/
|
||||
public synchronized boolean getBoolean(String key, boolean defaultValue) {
|
||||
String value = get(key);
|
||||
if (value == null) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
return Boolean.parseBoolean(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the int property value with a given default.
|
||||
*/
|
||||
public synchronized int getInt(String key, int defaultValue) {
|
||||
String value = get(key);
|
||||
if (value == null) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
return Integer.parseInt(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the long property value with a given default.
|
||||
*/
|
||||
public synchronized long getLong(String key, long defaultValue) {
|
||||
String value = get(key);
|
||||
if (value == null) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
return Long.parseLong(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the string property value with a given default.
|
||||
*/
|
||||
public synchronized String get(String key, String defaultValue) {
|
||||
String value = map.get(key.toLowerCase());
|
||||
return value == null ? defaultValue : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property value returning null if there is no value defined.
|
||||
*/
|
||||
public synchronized String get(String key) {
|
||||
return map.get(key.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Put all evaluating any expressions in the values.
|
||||
*/
|
||||
public synchronized void putEvalAll(Map<String, String> keyValueMap) {
|
||||
|
||||
for (Map.Entry<String, String> entry : keyValueMap.entrySet()) {
|
||||
putEval(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a single key value evaluating any expressions in the value.
|
||||
*/
|
||||
public synchronized String putEval(String key, String value) {
|
||||
value = PropertyExpression.eval(value, this);
|
||||
return map.put(key.toLowerCase(), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a single key value with no expression evaluation.
|
||||
*/
|
||||
public synchronized String put(String key, String value) {
|
||||
return map.put(key.toLowerCase(), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an entry.
|
||||
*/
|
||||
public synchronized String remove(String key) {
|
||||
return map.remove(key.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the entries.
|
||||
*/
|
||||
public synchronized Set<Entry<String, String>> entrySet() {
|
||||
return map.entrySet();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Helper used to load the ebean.properties into a PropertyMap.
|
||||
*/
|
||||
final class PropertyMapLoader {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PropertyMapLoader.class);
|
||||
|
||||
private static final Pattern OTHER_PROPS_REPLACE = Pattern.compile("\\", Pattern.LITERAL);
|
||||
|
||||
/**
|
||||
* Load the <code>test-ebean.properties</code>.
|
||||
*/
|
||||
public static PropertyMap loadTestProperties() {
|
||||
if (!PropertyMap.loadTestProperties()) {
|
||||
return new PropertyMap();
|
||||
}
|
||||
return load(null, "test-ebean.properties");
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the ebean.properties (and test-ebean.properties if present).
|
||||
*/
|
||||
public static PropertyMap loadGlobalProperties() {
|
||||
|
||||
boolean loadTestProperties = false;
|
||||
String fileName = System.getenv("EBEAN_PROPS_FILE");
|
||||
if (fileName == null) {
|
||||
fileName = System.getProperty("ebean.props.file");
|
||||
if (fileName == null) {
|
||||
loadTestProperties = PropertyMap.loadTestProperties();
|
||||
fileName = "ebean.properties";
|
||||
}
|
||||
}
|
||||
|
||||
PropertyMap map = load(null, fileName);
|
||||
if (loadTestProperties) {
|
||||
// load test properties if present in classpath
|
||||
map = load(map, "test-ebean.properties");
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the file returning the property map.
|
||||
*
|
||||
* @param p an existing property map to load into.
|
||||
* @param fileName the name of the properties file to load.
|
||||
*/
|
||||
public static PropertyMap load(PropertyMap p, String fileName) {
|
||||
|
||||
InputStream is = findInputStream(fileName);
|
||||
if (is == null) {
|
||||
return p;
|
||||
} else {
|
||||
return load(p, is);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the InputStream returning the property map.
|
||||
*
|
||||
* @param p an existing property map to load into.
|
||||
* @param in the InputStream of the properties file to load.
|
||||
*/
|
||||
public static PropertyMap load(PropertyMap p, InputStream in) {
|
||||
|
||||
Properties props = new Properties();
|
||||
try {
|
||||
props.load(in);
|
||||
in.close();
|
||||
return load(p, props);
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static PropertyMap load(PropertyMap p, Properties props) {
|
||||
|
||||
if (p == null) {
|
||||
p = new PropertyMap();
|
||||
}
|
||||
|
||||
// put values in initially without any evaluation
|
||||
for (Map.Entry<Object, Object> entry : props.entrySet()) {
|
||||
String key = ((String) entry.getKey()).toLowerCase();
|
||||
String val = ((String) entry.getValue());
|
||||
if (val != null) {
|
||||
val = val.trim();
|
||||
}
|
||||
p.put(key, val);
|
||||
}
|
||||
|
||||
p.evaluateProperties();
|
||||
|
||||
String otherProps = p.remove("load.properties");
|
||||
if (otherProps == null) {
|
||||
otherProps = p.remove("load.properties.override");
|
||||
}
|
||||
if (otherProps != null) {
|
||||
otherProps = OTHER_PROPS_REPLACE.matcher(otherProps).replaceAll(Matcher.quoteReplacement("/"));
|
||||
InputStream is = findInputStream(otherProps);
|
||||
if (is != null) {
|
||||
logger.debug("loading properties from {}", otherProps);
|
||||
load(p, is);
|
||||
} else {
|
||||
logger.error("load.properties " + otherProps + " not found.");
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the input stream given the file name.
|
||||
*/
|
||||
private static InputStream findInputStream(String fileName) {
|
||||
|
||||
if (fileName == null) {
|
||||
throw new NullPointerException("fileName is null?");
|
||||
}
|
||||
|
||||
try {
|
||||
File f = new File(fileName);
|
||||
|
||||
if (f.exists()) {
|
||||
logger.debug("{} found in file system", fileName);
|
||||
return new FileInputStream(f);
|
||||
} else {
|
||||
InputStream in = findInClassPath(fileName);
|
||||
if (in != null) {
|
||||
logger.debug("{} found in classpath", fileName);
|
||||
}
|
||||
return in;
|
||||
}
|
||||
|
||||
} catch (FileNotFoundException ex) {
|
||||
// already made the check so this
|
||||
// should never be thrown
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static InputStream findInClassPath(String fileName) {
|
||||
return Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,6 +12,8 @@ import io.ebean.cache.ServerCachePlugin;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DbEncrypt;
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.config.dbplatform.IdType;
|
||||
import io.ebean.config.properties.PropertiesLoader;
|
||||
import io.ebean.event.BeanFindController;
|
||||
import io.ebean.event.BeanPersistController;
|
||||
import io.ebean.event.BeanPersistListener;
|
||||
@@ -181,6 +183,11 @@ public class ServerConfig {
|
||||
*/
|
||||
private DatabasePlatform databasePlatform;
|
||||
|
||||
/**
|
||||
* The preferred IdType (to override the default Platform type).
|
||||
*/
|
||||
private IdType idType;
|
||||
|
||||
/**
|
||||
* For DB's using sequences this is the number of sequence values prefetched.
|
||||
*/
|
||||
@@ -286,7 +293,7 @@ public class ServerConfig {
|
||||
/**
|
||||
* When true create a read only DataSource using readOnlyDataSourceConfig defaulting values from dataSourceConfig.
|
||||
* I believe this will default to true in some future release (as it has a nice performance benefit).
|
||||
*
|
||||
* <p>
|
||||
* autoReadOnlyDataSource is an unfortunate name for this config option but I haven't come up with a better one.
|
||||
*/
|
||||
private boolean autoReadOnlyDataSource;
|
||||
@@ -365,6 +372,16 @@ public class ServerConfig {
|
||||
*/
|
||||
private DbTypeConfig dbTypeConfig = new DbTypeConfig();
|
||||
|
||||
/**
|
||||
* The UUID version to use.
|
||||
*/
|
||||
private UuidVersion uuidVersion = UuidVersion.VERSION4;
|
||||
|
||||
/**
|
||||
* The UUID state file (for Version 1 UUIDs).
|
||||
*/
|
||||
private String uuidStateFile = "ebean-uuid.state";
|
||||
|
||||
private List<IdGenerator> idGenerators = new ArrayList<>();
|
||||
private List<BeanFindController> findControllers = new ArrayList<>();
|
||||
private List<BeanPersistController> persistControllers = new ArrayList<>();
|
||||
@@ -463,6 +480,19 @@ public class ServerConfig {
|
||||
*/
|
||||
private boolean disableL2Cache;
|
||||
|
||||
|
||||
/**
|
||||
* Should the javax.validation.constraints.NotNull enforce a notNull column in DB.
|
||||
* If set to false, use io.ebean.annotation.NotNull or Column(nullable=true).
|
||||
*/
|
||||
private boolean useJavaxValidationNotNull = true;
|
||||
|
||||
/**
|
||||
* Generally we want to perform L2 cache notification in the background and not impact
|
||||
* the performance of executing transactions.
|
||||
*/
|
||||
private boolean notifyL2CacheInForeground;
|
||||
|
||||
/**
|
||||
* The time in millis used to determine when a query is alerted for being slow.
|
||||
*/
|
||||
@@ -473,9 +503,13 @@ public class ServerConfig {
|
||||
*/
|
||||
private SlowQueryListener slowQueryListener;
|
||||
|
||||
|
||||
private ProfilingConfig profilingConfig = new ProfilingConfig();
|
||||
|
||||
/**
|
||||
* Controls the default order by id setting of queries. See {@link Query#orderById(boolean)}
|
||||
*/
|
||||
private boolean defaultOrderById = false;
|
||||
|
||||
/**
|
||||
* Construct a Server Configuration for programmatically creating an EbeanServer.
|
||||
*/
|
||||
@@ -511,6 +545,21 @@ public class ServerConfig {
|
||||
this.slowQueryListener = slowQueryListener;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the default orderById setting for queries.
|
||||
*/
|
||||
public void setDefaultOrderById(boolean defaultOrderById) {
|
||||
this.defaultOrderById = defaultOrderById;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default orderById setting for queries.
|
||||
*/
|
||||
public boolean isDefaultOrderById() {
|
||||
return defaultOrderById;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a service object into configuration such that it can be passed to a plugin.
|
||||
* <p>
|
||||
@@ -1716,6 +1765,20 @@ public class ServerConfig {
|
||||
this.databasePlatform = databasePlatform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the preferred DB platform IdType.
|
||||
*/
|
||||
public IdType getIdType() {
|
||||
return idType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the preferred DB platform IdType.
|
||||
*/
|
||||
public void setIdType(IdType idType) {
|
||||
this.idType = idType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the EncryptKeyManager.
|
||||
*/
|
||||
@@ -1837,6 +1900,34 @@ public class ServerConfig {
|
||||
this.dbTypeConfig.setDbUuid(dbUuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the UUID version mode.
|
||||
*/
|
||||
public UuidVersion getUuidVersion() {
|
||||
return uuidVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the UUID version mode.
|
||||
*/
|
||||
public void setUuidVersion(UuidVersion uuidVersion) {
|
||||
this.uuidVersion = uuidVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the UUID state file.
|
||||
*/
|
||||
public String getUuidStateFile() {
|
||||
return uuidStateFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the UUID state file.
|
||||
*/
|
||||
public void setUuidStateFile(String uuidStateFile) {
|
||||
this.uuidStateFile = uuidStateFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if LocalTime should be persisted with nanos precision.
|
||||
*/
|
||||
@@ -2551,7 +2642,8 @@ public class ServerConfig {
|
||||
* Load settings from ebean.properties.
|
||||
*/
|
||||
public void loadFromProperties() {
|
||||
loadFromProperties(PropertyMap.defaultProperties());
|
||||
this.properties = PropertiesLoader.load();
|
||||
configureFromProperties();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2559,24 +2651,41 @@ public class ServerConfig {
|
||||
*/
|
||||
public void loadFromProperties(Properties properties) {
|
||||
// keep the properties used for configuration so that these are available for plugins
|
||||
this.properties = properties;
|
||||
this.properties = PropertiesLoader.eval(properties);
|
||||
configureFromProperties();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the settings from the given properties
|
||||
*/
|
||||
private void configureFromProperties() {
|
||||
autoConfiguration();
|
||||
PropertiesWrapper p = new PropertiesWrapper("ebean", name, properties);
|
||||
loadSettings(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a 'plugin' to provide automatic configuration. Intended for automatic testing
|
||||
* configuration with Docker containers via ebean-test-config.
|
||||
*/
|
||||
private void autoConfiguration() {
|
||||
for (AutoConfigure autoConfigure : serviceLoad(AutoConfigure.class)) {
|
||||
autoConfigure.configure(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated - this does nothing now, we always try to read test configuration.
|
||||
* <p>
|
||||
* Load settings from test-ebean.properties and do nothing if the properties is not found.
|
||||
* <p>
|
||||
* This is typically used when test-ebean.properties is put into the test class path and used
|
||||
* to configure Ebean for running tests.
|
||||
* </p>
|
||||
*/
|
||||
@Deprecated
|
||||
public void loadTestProperties() {
|
||||
Properties properties = PropertyMap.testProperties();
|
||||
if (!properties.isEmpty()) {
|
||||
PropertiesWrapper p = new PropertiesWrapper("ebean", name, properties);
|
||||
loadSettings(p);
|
||||
}
|
||||
// do nothing now ... as we always try to read test configuration and that should only
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2623,6 +2732,7 @@ public class ServerConfig {
|
||||
*/
|
||||
protected void loadDataSourceSettings(PropertiesWrapper p) {
|
||||
dataSourceConfig.loadSettings(p.properties, name);
|
||||
readOnlyDataSourceConfig.loadSettings(p.properties, name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2680,9 +2790,11 @@ public class ServerConfig {
|
||||
slowQueryMillis = p.getLong("slowQueryMillis", slowQueryMillis);
|
||||
docStoreOnly = p.getBoolean("docStoreOnly", docStoreOnly);
|
||||
disableL2Cache = p.getBoolean("disableL2Cache", disableL2Cache);
|
||||
notifyL2CacheInForeground = p.getBoolean("notifyL2CacheInForeground", notifyL2CacheInForeground);
|
||||
explicitTransactionBeginMode = p.getBoolean("explicitTransactionBeginMode", explicitTransactionBeginMode);
|
||||
autoCommitMode = p.getBoolean("autoCommitMode", autoCommitMode);
|
||||
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
|
||||
useJavaxValidationNotNull = p.getBoolean("useJavaxValidationNotNull", useJavaxValidationNotNull);
|
||||
autoReadOnlyDataSource = p.getBoolean("autoReadOnlyDataSource", autoReadOnlyDataSource);
|
||||
|
||||
backgroundExecutorSchedulePoolSize = p.getInt("backgroundExecutorSchedulePoolSize", backgroundExecutorSchedulePoolSize);
|
||||
@@ -2716,6 +2828,7 @@ public class ServerConfig {
|
||||
throw new IllegalArgumentException("Property 'batch.mode' or 'persistBatching' is being set but no longer used. Please change to use 'persistBatchMode'");
|
||||
}
|
||||
|
||||
idType = p.getEnum(IdType.class, "idType", idType);
|
||||
persistBatch = p.getEnum(PersistBatch.class, "persistBatch", persistBatch);
|
||||
persistBatchOnCascade = p.getEnum(PersistBatch.class, "persistBatchOnCascade", persistBatchOnCascade);
|
||||
|
||||
@@ -2740,6 +2853,7 @@ public class ServerConfig {
|
||||
databaseBooleanTrue = p.get("databaseBooleanTrue", databaseBooleanTrue);
|
||||
databaseBooleanFalse = p.get("databaseBooleanFalse", databaseBooleanFalse);
|
||||
databasePlatformName = p.get("databasePlatformName", databasePlatformName);
|
||||
defaultOrderById = p.getBoolean("defaultOrderById", defaultOrderById);
|
||||
|
||||
DbUuid dbUuid = p.getEnum(DbUuid.class, "dbuuid", null);
|
||||
if (dbUuid != null) {
|
||||
@@ -2748,6 +2862,10 @@ public class ServerConfig {
|
||||
if (p.getBoolean("uuidStoreAsBinary", false)) {
|
||||
dbTypeConfig.setDbUuid(DbUuid.BINARY);
|
||||
}
|
||||
|
||||
uuidVersion = p.getEnum(UuidVersion.class, "uuidVersion", uuidVersion);
|
||||
uuidStateFile = p.get("uuidStateFile", uuidStateFile);
|
||||
|
||||
localTimeWithNanos = p.getBoolean("localTimeWithNanos", localTimeWithNanos);
|
||||
jodaLocalTimeMode = p.get("jodaLocalTimeMode", jodaLocalTimeMode);
|
||||
|
||||
@@ -2768,6 +2886,22 @@ public class ServerConfig {
|
||||
ddlInitSql = p.get("ddl.initSql", ddlInitSql);
|
||||
ddlSeedSql = p.get("ddl.seedSql", ddlSeedSql);
|
||||
|
||||
// read tenant-configuration from config:
|
||||
// tenant.mode = NONE | DB | SCHEMA | CATALOG | PARTITION
|
||||
String mode = p.get("tenant.mode");
|
||||
if (mode != null) {
|
||||
for (TenantMode value : TenantMode.values()) {
|
||||
if (value.name().equalsIgnoreCase(mode)) {
|
||||
tenantMode = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentTenantProvider = createInstance(p, CurrentTenantProvider.class, "tenant.currentTenantProvider", currentTenantProvider);
|
||||
tenantCatalogProvider = createInstance(p, TenantCatalogProvider.class, "tenant.catalogProvider", tenantCatalogProvider);
|
||||
tenantSchemaProvider = createInstance(p, TenantSchemaProvider.class, "tenant.schemaProvider", tenantSchemaProvider);
|
||||
tenantPartitionColumn = p.get("tenant.partitionColumn", tenantPartitionColumn);
|
||||
classes = getClasses(p);
|
||||
}
|
||||
|
||||
@@ -2900,6 +3034,44 @@ public class ServerConfig {
|
||||
this.disableL2Cache = disableL2Cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if we use javax.validation.constraints.NotNull
|
||||
*/
|
||||
public boolean isUseJavaxValidationNotNull() {
|
||||
return useJavaxValidationNotNull;
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls if Ebean should ignore <code>&x64;javax.validation.contstraints.NotNull</code>
|
||||
* with respect to generating a <code>NOT NULL</code> column.
|
||||
* <p>
|
||||
* Normally when Ebean sees javax NotNull annotation it means that column is defined as NOT NULL.
|
||||
* Set this to <code>false</code> and the javax NotNull annotation is effectively ignored (and
|
||||
* we instead use Ebean's own NotNull annotation or JPA Column(nullable=false) annotation.
|
||||
*/
|
||||
public void setUseJavaxValidationNotNull(boolean useJavaxValidationNotNull) {
|
||||
this.useJavaxValidationNotNull = useJavaxValidationNotNull;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if L2 cache notification should run in the foreground.
|
||||
*/
|
||||
public boolean isNotifyL2CacheInForeground() {
|
||||
return notifyL2CacheInForeground;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this to true to run L2 cache notification in the foreground.
|
||||
* <p>
|
||||
* In general we don't want to do that as when we use a distributed cache (like Ignite, Hazelcast etc)
|
||||
* we are making network calls and we prefer to do this in background and not impact the response time
|
||||
* of the executing transaction.
|
||||
* </p>
|
||||
*/
|
||||
public void setNotifyL2CacheInForeground(boolean notifyL2CacheInForeground) {
|
||||
this.notifyL2CacheInForeground = notifyL2CacheInForeground;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query plan time to live.
|
||||
*/
|
||||
@@ -2930,32 +3102,45 @@ public class ServerConfig {
|
||||
*/
|
||||
public enum DbUuid {
|
||||
|
||||
|
||||
/**
|
||||
* Store using native UUID in H2 and Postgres and otherwise fallback to VARCHAR(40).
|
||||
*/
|
||||
AUTO_VARCHAR(true, false),
|
||||
AUTO_VARCHAR(true, false, false),
|
||||
|
||||
/**
|
||||
* Store using native UUID in H2 and Postgres and otherwise fallback to BINARY(16).
|
||||
*/
|
||||
AUTO_BINARY(true, true),
|
||||
AUTO_BINARY(true, true, false),
|
||||
|
||||
/**
|
||||
* Store using native UUID in H2 and Postgres and otherwise fallback to BINARY(16) with optimized packing.
|
||||
*/
|
||||
AUTO_BINARY_OPTIMIZED(true, true, true),
|
||||
|
||||
/**
|
||||
* Store using DB VARCHAR(40).
|
||||
*/
|
||||
VARCHAR(false, false),
|
||||
VARCHAR(false, false, false),
|
||||
|
||||
/**
|
||||
* Store using DB BINARY(16).
|
||||
*/
|
||||
BINARY(false, true);
|
||||
BINARY(false, true, false),
|
||||
|
||||
/**
|
||||
* Store using DB BINARY(16).
|
||||
*/
|
||||
BINARY_OPTIMIZED(false, true, true);
|
||||
|
||||
boolean nativeType;
|
||||
boolean binary;
|
||||
boolean binaryOptimized;
|
||||
|
||||
DbUuid(boolean nativeType, boolean binary) {
|
||||
DbUuid(boolean nativeType, boolean binary, boolean binaryOptimized) {
|
||||
this.nativeType = nativeType;
|
||||
this.binary = binary;
|
||||
this.binaryOptimized = binaryOptimized;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2971,5 +3156,18 @@ public class ServerConfig {
|
||||
public boolean useBinary() {
|
||||
return binary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true, if optimized packing should be used.
|
||||
*/
|
||||
public boolean useBinaryOptimized() {
|
||||
return binaryOptimized;
|
||||
}
|
||||
}
|
||||
|
||||
public enum UuidVersion {
|
||||
VERSION4,
|
||||
VERSION1,
|
||||
VERSION1RND
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,32 +8,40 @@ public enum TenantMode {
|
||||
/**
|
||||
* No multi-tenancy.
|
||||
*/
|
||||
NONE(false),
|
||||
NONE(false, true),
|
||||
|
||||
/**
|
||||
* Each Tenant has their own Database (javax.sql.DataSource)
|
||||
*/
|
||||
DB(true),
|
||||
DB(true, false),
|
||||
|
||||
/**
|
||||
* Each Tenant has their own Database schema.
|
||||
*/
|
||||
SCHEMA(true),
|
||||
SCHEMA(true, false),
|
||||
|
||||
/**
|
||||
* Each Tenant has their own Database but with in connection pool
|
||||
*/
|
||||
CATALOG(true),
|
||||
CATALOG(true, false),
|
||||
|
||||
/**
|
||||
* Tenants share tables but have a discriminator/partition column that partitions the data.
|
||||
*/
|
||||
PARTITION(false);
|
||||
PARTITION(false, true),
|
||||
|
||||
/**
|
||||
* Each Tenant has their own Database (javax.sql.DataSource), and there is also one master-database
|
||||
* (that holds configuration e.g.)
|
||||
*/
|
||||
DB_WITH_MASTER(true, true);
|
||||
|
||||
boolean dynamicDataSource;
|
||||
boolean ddlEnabled;
|
||||
|
||||
TenantMode(boolean dynamicDataSource) {
|
||||
TenantMode(boolean dynamicDataSource, boolean ddlEnabled) {
|
||||
this.dynamicDataSource = dynamicDataSource;
|
||||
this.ddlEnabled = ddlEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,4 +50,12 @@ public enum TenantMode {
|
||||
public boolean isDynamicDataSource() {
|
||||
return dynamicDataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true, if DDL is enabled.
|
||||
*/
|
||||
public boolean isDdlEnabled() {
|
||||
return ddlEnabled;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import io.ebean.annotation.PersistBatch;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.CustomDbTypeMapping;
|
||||
import io.ebean.config.DbTypeConfig;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.util.JdbcClose;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -98,6 +100,10 @@ public class DatabasePlatform {
|
||||
*/
|
||||
protected DbIdentity dbIdentity = new DbIdentity();
|
||||
|
||||
protected boolean sequenceBatchMode = true;
|
||||
|
||||
protected int sequenceBatchSize = 20;
|
||||
|
||||
/**
|
||||
* The history support for this database platform.
|
||||
*/
|
||||
@@ -140,9 +146,25 @@ public class DatabasePlatform {
|
||||
private static final char BACK_TICK = '`';
|
||||
|
||||
/**
|
||||
* The like clause. Can be overridden to disable default escape character.
|
||||
* The non-escaped like clause (to stop slash being escaped on some platforms).
|
||||
* Used for the 'raw like' expression but not for startsWith, endsWith and contains expressions.
|
||||
*/
|
||||
protected String likeClause = "like ?";
|
||||
protected String likeClauseRaw = "like ? escape''";
|
||||
|
||||
/**
|
||||
* Escaped like clause for startsWith, endsWith and contains.
|
||||
*/
|
||||
protected String likeClauseEscaped = "like ? escape'|'";
|
||||
|
||||
/**
|
||||
* Escape character used for startsWith, endsWith and contains.
|
||||
*/
|
||||
protected char likeEscapeChar = '|';
|
||||
|
||||
/**
|
||||
* Characters escaped for startsWith, endsWith and contains.
|
||||
*/
|
||||
protected char[] likeSpecialCharacters = { '%', '_', '|' };
|
||||
|
||||
protected DbEncrypt dbEncrypt;
|
||||
|
||||
@@ -177,8 +199,6 @@ public class DatabasePlatform {
|
||||
|
||||
protected SqlExceptionTranslator exceptionTranslator = new SqlCodeTranslator();
|
||||
|
||||
protected char[] specialLikeCharacters = { '%', '_', '\\' };
|
||||
|
||||
/**
|
||||
* Instantiates a new database platform.
|
||||
*/
|
||||
@@ -192,10 +212,19 @@ public class DatabasePlatform {
|
||||
return exceptionTranslator.translate(message, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the platform given the server configuration.
|
||||
*/
|
||||
public void configure(ServerConfig config) {
|
||||
this.sequenceBatchSize = config.getDatabaseSequenceBatchSize();
|
||||
configureIdType(config.getIdType());
|
||||
configure(config.getDbTypeConfig(), config.isAllQuotedIdentifiers());
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure UUID Storage etc based on ServerConfig settings.
|
||||
*/
|
||||
public void configure(DbTypeConfig config, boolean allQuotedIdentifiers) {
|
||||
protected void configure(DbTypeConfig config, boolean allQuotedIdentifiers) {
|
||||
this.allQuotedIdentifiers = allQuotedIdentifiers;
|
||||
addGeoTypes(config.getGeometrySRID());
|
||||
configureIdType(config.getIdType());
|
||||
@@ -249,6 +278,20 @@ public class DatabasePlatform {
|
||||
return platform.name().toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if we are using Sequence batch mode rather than STEP.
|
||||
*/
|
||||
public boolean isSequenceBatchMode() {
|
||||
return sequenceBatchMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to false to not use sequence batch mode but instead STEP mode.
|
||||
*/
|
||||
public void setSequenceBatchMode(boolean sequenceBatchMode) {
|
||||
this.sequenceBatchMode = sequenceBatchMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this database platform supports native ILIKE expression.
|
||||
*/
|
||||
@@ -290,10 +333,10 @@ public class DatabasePlatform {
|
||||
* @param be the BackgroundExecutor that can be used to load the sequence if
|
||||
* desired
|
||||
* @param ds the DataSource
|
||||
* @param stepSize the sequence allocation size as defined by mapping (defaults to 50)
|
||||
* @param seqName the name of the sequence
|
||||
* @param batchSize the number of sequences that should be loaded
|
||||
*/
|
||||
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
|
||||
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, int stepSize, String seqName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -572,6 +615,14 @@ public class DatabasePlatform {
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* For update hint on the FROM clause (SQL server only).
|
||||
*/
|
||||
public String fromForUpdate(Query.ForUpdate forUpdateMode) {
|
||||
// return null except for sql server
|
||||
return null;
|
||||
}
|
||||
|
||||
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode) {
|
||||
// silently assume the database does not support the "for update" clause.
|
||||
logger.info("it seems your database does not support the 'for update' clause");
|
||||
@@ -583,8 +634,8 @@ public class DatabasePlatform {
|
||||
* <p>
|
||||
* This may include an escape clause to disable a default escape character.
|
||||
*/
|
||||
public String getLikeClause() {
|
||||
return likeClause;
|
||||
public String getLikeClause(boolean rawLikeExpression) {
|
||||
return rawLikeExpression ? likeClauseRaw : likeClauseEscaped;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -604,18 +655,7 @@ public class DatabasePlatform {
|
||||
try {
|
||||
return tables.next();
|
||||
} finally {
|
||||
close(tables);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the resultSet.
|
||||
*/
|
||||
protected void close(ResultSet resultSet) {
|
||||
try {
|
||||
resultSet.close();
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error closing resultSet", e);
|
||||
JdbcClose.close(tables);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -627,7 +667,7 @@ public class DatabasePlatform {
|
||||
for (int i = 0; i < value.length(); i++) {
|
||||
char ch = value.charAt(i);
|
||||
boolean escaped = false;
|
||||
for (char escapeChar: specialLikeCharacters) {
|
||||
for (char escapeChar: likeSpecialCharacters) {
|
||||
if (ch == escapeChar) {
|
||||
if (sb == null) {
|
||||
sb = new StringBuilder(value.substring(0, i));
|
||||
@@ -649,6 +689,6 @@ public class DatabasePlatform {
|
||||
}
|
||||
|
||||
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
|
||||
sb.append('\\').append(ch);
|
||||
sb.append(likeEscapeChar).append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,27 +126,33 @@ public class DbPlatformType implements ExtraDbTypes {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(name);
|
||||
|
||||
if (canHaveLength || !strict) {
|
||||
// see if there is a precision/scale to add (or not)
|
||||
int len = deployLength != 0 ? deployLength : defaultLength;
|
||||
if (len == Integer.MAX_VALUE) {
|
||||
sb.append("(max)"); // TODO: this is sqlserver specific
|
||||
} else if (len > 0) {
|
||||
sb.append("(");
|
||||
sb.append(len);
|
||||
int scale = deployScale != 0 ? deployScale : defaultScale;
|
||||
if (scale > 0) {
|
||||
sb.append(",");
|
||||
sb.append(scale);
|
||||
}
|
||||
sb.append(")");
|
||||
}
|
||||
renderLengthScale(deployLength, deployScale, sb);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the length and scale part of the column definition.
|
||||
*/
|
||||
protected void renderLengthScale(int deployLength, int deployScale, StringBuilder sb) {
|
||||
// see if there is a precision/scale to add (or not)
|
||||
int len = deployLength != 0 ? deployLength : defaultLength;
|
||||
if (len == Integer.MAX_VALUE) {
|
||||
sb.append("(max)"); // TODO: this is sqlserver specific
|
||||
} else if (len > 0) {
|
||||
sb.append("(");
|
||||
sb.append(len);
|
||||
int scale = deployScale != 0 ? deployScale : defaultScale;
|
||||
if (scale > 0) {
|
||||
sb.append(",");
|
||||
sb.append(scale);
|
||||
}
|
||||
sb.append(")");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a copy of the type with a new default length.
|
||||
*/
|
||||
|
||||
@@ -10,8 +10,23 @@ import java.util.Map;
|
||||
*/
|
||||
public class DbPlatformTypeMapping {
|
||||
|
||||
/**
|
||||
* Boolean type used for logical model.
|
||||
*/
|
||||
private static class BooleanLogicalType extends DbPlatformType {
|
||||
BooleanLogicalType() {
|
||||
super("boolean", false);
|
||||
}
|
||||
@Override
|
||||
protected void renderLengthScale(int deployLength, int deployScale, StringBuilder sb) {
|
||||
// do not have length - even if platform boolean type does like integer(1)
|
||||
}
|
||||
}
|
||||
|
||||
private static DbPlatformTypeLookup lookup = new DbPlatformTypeLookup();
|
||||
|
||||
private static final DbPlatformType BOOLEAN_LOGICAL = new BooleanLogicalType();
|
||||
|
||||
private static final DbPlatformType UUID_NATIVE = new DbPlatformType("uuid", false);
|
||||
@SuppressWarnings("unused")
|
||||
private static final DbPlatformType UUID_PLACEHOLDER = new DbPlatformType("uuidPlaceholder");
|
||||
@@ -51,7 +66,7 @@ public class DbPlatformTypeMapping {
|
||||
*/
|
||||
private void loadDefaults(boolean logicalTypes) {
|
||||
|
||||
put(DbType.BOOLEAN);
|
||||
put(DbType.BOOLEAN, BOOLEAN_LOGICAL);
|
||||
put(DbType.BIT);
|
||||
put(DbType.INTEGER);
|
||||
put(DbType.BIGINT);
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package io.ebean.config.dbplatform;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Database sequence based IdGenerator using Sequence Step 1 but batch fetch many sequence values.
|
||||
*/
|
||||
public abstract class SequenceBatchIdGenerator extends SequenceIdGenerator {
|
||||
|
||||
/**
|
||||
* Construct where batchSize is the sequence step size.
|
||||
*
|
||||
*/
|
||||
public SequenceBatchIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
|
||||
super(be, ds, seqName, batchSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* If allocateSize is large load some sequences in a background thread.
|
||||
* <p>
|
||||
* For example, when inserting a bean with a cascade on a OneToMany with many
|
||||
* beans Ebean can call this to ensure .
|
||||
* </p>
|
||||
*/
|
||||
@Override
|
||||
public void preAllocateIds(int requestSize) {
|
||||
if (allocationSize > 1 && requestSize > allocationSize) {
|
||||
// only bother if allocateSize is bigger than
|
||||
// the normal loading batchSize
|
||||
if (requestSize > 100) {
|
||||
// max out at 100 for now
|
||||
requestSize = 100;
|
||||
}
|
||||
loadInBackground(requestSize);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the next set of Ids as the next value plus all the following numbers up to the step size.
|
||||
*/
|
||||
@Override
|
||||
protected List<Long> readIds(ResultSet resultSet, int loadSize) throws SQLException {
|
||||
|
||||
List<Long> newIds = new ArrayList<>(loadSize);
|
||||
while (resultSet.next()) {
|
||||
newIds.add(resultSet.getLong(1));
|
||||
}
|
||||
return newIds;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package io.ebean.config.dbplatform;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.util.JdbcClose;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -12,13 +13,15 @@ import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Database sequence based IdGenerator.
|
||||
*/
|
||||
public abstract class SequenceIdGenerator implements PlatformIdGenerator {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SequenceIdGenerator.class);
|
||||
protected static final Logger logger = LoggerFactory.getLogger("io.ebean.SEQ");
|
||||
|
||||
/**
|
||||
* Used to synchronise the idList access.
|
||||
@@ -41,18 +44,18 @@ public abstract class SequenceIdGenerator implements PlatformIdGenerator {
|
||||
|
||||
protected final ArrayList<Long> idList = new ArrayList<>(50);
|
||||
|
||||
protected final int batchSize;
|
||||
protected final int allocationSize;
|
||||
|
||||
protected int currentlyBackgroundLoading;
|
||||
protected boolean currentlyBackgroundLoading;
|
||||
|
||||
/**
|
||||
* Construct given a dataSource and sql to return the next sequence value.
|
||||
*/
|
||||
public SequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
|
||||
protected SequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int allocationSize) {
|
||||
this.backgroundExecutor = be;
|
||||
this.dataSource = ds;
|
||||
this.seqName = seqName;
|
||||
this.batchSize = batchSize;
|
||||
this.allocationSize = allocationSize;
|
||||
}
|
||||
|
||||
public abstract String getSql(int batchSize);
|
||||
@@ -81,26 +84,8 @@ public abstract class SequenceIdGenerator implements PlatformIdGenerator {
|
||||
* </p>
|
||||
*/
|
||||
@Override
|
||||
public void preAllocateIds(int allocateSize) {
|
||||
if (batchSize > 1 && allocateSize > batchSize) {
|
||||
// only bother if allocateSize is bigger than
|
||||
// the normal loading batchSize
|
||||
if (allocateSize > 100) {
|
||||
// max out at 100 for now
|
||||
allocateSize = 100;
|
||||
}
|
||||
loadLargeAllocation(allocateSize);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by preAllocateIds when we know that a large number of Id's is going
|
||||
* to be needed shortly.
|
||||
*/
|
||||
protected void loadLargeAllocation(final int allocateSize) {
|
||||
// preAllocateIds was called with a relatively large batchSize
|
||||
// so we will just go ahead and load those anyway in background
|
||||
backgroundExecutor.execute(() -> loadMoreIds(allocateSize, null));
|
||||
public void preAllocateIds(int requestSize) {
|
||||
// do nothing by default
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,80 +98,80 @@ public abstract class SequenceIdGenerator implements PlatformIdGenerator {
|
||||
public Object nextId(Transaction t) {
|
||||
synchronized (monitor) {
|
||||
|
||||
if (idList.isEmpty()) {
|
||||
loadMoreIds(batchSize, t);
|
||||
}
|
||||
Long nextId = idList.remove(0);
|
||||
|
||||
if (batchSize > 1) {
|
||||
if (idList.size() <= batchSize / 2) {
|
||||
loadBatchInBackground();
|
||||
}
|
||||
int size = idList.size();
|
||||
if (size > 0) {
|
||||
maybeLoadMoreInBackground(size);
|
||||
} else {
|
||||
loadMore(allocationSize);
|
||||
}
|
||||
|
||||
return nextId;
|
||||
return idList.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load another batch of Id's using a background thread.
|
||||
*/
|
||||
protected void loadBatchInBackground() {
|
||||
|
||||
// single threaded processing...
|
||||
synchronized (backgroundLoadMonitor) {
|
||||
|
||||
if (currentlyBackgroundLoading > 0) {
|
||||
// skip as already background loading
|
||||
logger.debug("... skip background sequence load (another load in progress)");
|
||||
return;
|
||||
private void maybeLoadMoreInBackground(int currentSize) {
|
||||
if (allocationSize > 1) {
|
||||
if (currentSize <= allocationSize / 2) {
|
||||
loadInBackground(allocationSize);
|
||||
}
|
||||
|
||||
currentlyBackgroundLoading = batchSize;
|
||||
|
||||
backgroundExecutor.execute(() -> {
|
||||
loadMoreIds(batchSize, null);
|
||||
synchronized (backgroundLoadMonitor) {
|
||||
currentlyBackgroundLoading = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected void loadMoreIds(final int numberToLoad, Transaction t) {
|
||||
|
||||
ArrayList<Long> newIds = getMoreIds(numberToLoad, t);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("... seq:" + seqName + " loaded:" + numberToLoad + " ids:" + newIds);
|
||||
}
|
||||
|
||||
private void loadMore(int requestSize) {
|
||||
List<Long> newIds = getMoreIds(requestSize);
|
||||
synchronized (monitor) {
|
||||
idList.addAll(newIds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load another batch of Id's using a background thread.
|
||||
*/
|
||||
protected void loadInBackground(final int requestSize) {
|
||||
|
||||
// single threaded processing...
|
||||
synchronized (backgroundLoadMonitor) {
|
||||
if (currentlyBackgroundLoading) {
|
||||
// skip as already background loading
|
||||
logger.debug("... skip background sequence load (another load in progress)");
|
||||
return;
|
||||
}
|
||||
|
||||
currentlyBackgroundLoading = true;
|
||||
|
||||
backgroundExecutor.execute(() -> {
|
||||
loadMore(requestSize);
|
||||
synchronized (backgroundLoadMonitor) {
|
||||
currentlyBackgroundLoading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the resultSet returning the list of Id values.
|
||||
*/
|
||||
protected abstract List<Long> readIds(ResultSet resultSet, int loadSize) throws SQLException;
|
||||
|
||||
/**
|
||||
* Get more Id's by executing a query and reading the Id's returned.
|
||||
*/
|
||||
protected ArrayList<Long> getMoreIds(int loadSize, Transaction t) {
|
||||
protected List<Long> getMoreIds(int requestSize) {
|
||||
|
||||
String sql = getSql(loadSize);
|
||||
String sql = getSql(requestSize);
|
||||
|
||||
ArrayList<Long> newIds = new ArrayList<>(loadSize);
|
||||
|
||||
boolean useTxnConnection = t != null;
|
||||
|
||||
Connection c = null;
|
||||
PreparedStatement pstmt = null;
|
||||
ResultSet rset = null;
|
||||
Connection connection = null;
|
||||
PreparedStatement statement = null;
|
||||
ResultSet resultSet = null;
|
||||
try {
|
||||
c = useTxnConnection ? t.getConnection() : dataSource.getConnection();
|
||||
connection = dataSource.getConnection();
|
||||
|
||||
pstmt = c.prepareStatement(sql);
|
||||
rset = pstmt.executeQuery();
|
||||
while (rset.next()) {
|
||||
newIds.add(rset.getLong(1));
|
||||
statement = connection.prepareStatement(sql);
|
||||
resultSet = statement.executeQuery();
|
||||
|
||||
List<Long> newIds = readIds(resultSet, requestSize);
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("seq:{} loaded:{} sql:{}", seqName, newIds.size(), sql);
|
||||
}
|
||||
if (newIds.isEmpty()) {
|
||||
throw new PersistenceException("Always expecting more than 1 row from " + sql);
|
||||
@@ -197,46 +182,24 @@ public abstract class SequenceIdGenerator implements PlatformIdGenerator {
|
||||
} catch (SQLException e) {
|
||||
if (e.getMessage().contains("Database is already closed")) {
|
||||
String msg = "Error getting SEQ when DB shutting down " + e.getMessage();
|
||||
logger.info(msg);
|
||||
logger.error(msg);
|
||||
System.out.println(msg);
|
||||
return newIds;
|
||||
return Collections.emptyList();
|
||||
} else {
|
||||
throw new PersistenceException("Error getting sequence nextval", e);
|
||||
}
|
||||
} finally {
|
||||
if (useTxnConnection) {
|
||||
closeResources(null, pstmt, rset);
|
||||
} else {
|
||||
closeResources(c, pstmt, rset);
|
||||
}
|
||||
closeResources(connection, statement, resultSet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the JDBC resources.
|
||||
*/
|
||||
protected void closeResources(Connection c, PreparedStatement pstmt, ResultSet rset) {
|
||||
try {
|
||||
if (rset != null) {
|
||||
rset.close();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error closing ResultSet", e);
|
||||
}
|
||||
try {
|
||||
if (pstmt != null) {
|
||||
pstmt.close();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error closing PreparedStatement", e);
|
||||
}
|
||||
try {
|
||||
if (c != null) {
|
||||
c.close();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error closing Connection", e);
|
||||
}
|
||||
private void closeResources(Connection connection, PreparedStatement statement, ResultSet resultSet) {
|
||||
JdbcClose.close(resultSet);
|
||||
JdbcClose.close(statement);
|
||||
JdbcClose.close(connection);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package io.ebean.config.dbplatform;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Database sequence based IdGenerator using Sequence Step (e.g. step 50).
|
||||
*/
|
||||
public abstract class SequenceStepIdGenerator extends SequenceIdGenerator {
|
||||
|
||||
/**
|
||||
* Construct with stepSize (typically 50).
|
||||
*/
|
||||
protected SequenceStepIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int stepSize) {
|
||||
super(be, ds, seqName, stepSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the next set of Ids as the next value plus all the following numbers up to the step size.
|
||||
*/
|
||||
@Override
|
||||
protected List<Long> readIds(ResultSet resultSet, int ignoreRequestSize) throws SQLException {
|
||||
|
||||
List<Long> newIds = new ArrayList<>(allocationSize);
|
||||
if (resultSet.next()) {
|
||||
long start = resultSet.getLong(1);
|
||||
for (int i = 0; i < allocationSize; i++) {
|
||||
newIds.add(start + i);
|
||||
}
|
||||
}
|
||||
return newIds;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.ebean.config.dbplatform;
|
||||
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.util.JdbcClose;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -82,27 +83,9 @@ public class SimpleSequenceIdGenerator implements PlatformIdGenerator {
|
||||
}
|
||||
|
||||
private void closeResources(ResultSet rset, PreparedStatement pstmt, Connection c) {
|
||||
try {
|
||||
if (rset != null) {
|
||||
rset.close();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error closing ResultSet", e);
|
||||
}
|
||||
try {
|
||||
if (pstmt != null) {
|
||||
pstmt.close();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error closing PreparedStatement", e);
|
||||
}
|
||||
try {
|
||||
if (c != null) {
|
||||
c.close();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error closing Connection", e);
|
||||
}
|
||||
JdbcClose.close(rset);
|
||||
JdbcClose.close(pstmt);
|
||||
JdbcClose.close(c);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,10 +26,7 @@ public class DB2Platform extends DatabasePlatform {
|
||||
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(true);
|
||||
this.dbIdentity.setSupportsSequence(true);
|
||||
|
||||
this.likeClause = "like ? escape '|'";
|
||||
this.specialLikeCharacters = new char[] { '%', '_', '|' };
|
||||
|
||||
|
||||
this.exceptionTranslator =
|
||||
new SqlErrorCodes()
|
||||
.addAcquireLock("40001","57033") // key -911/-913
|
||||
@@ -49,19 +46,14 @@ public class DB2Platform extends DatabasePlatform {
|
||||
persistBatchOnCascade = PersistBatch.NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
|
||||
sb.append('|').append(ch);
|
||||
}
|
||||
/**
|
||||
* Return a DB2 specific sequence IdGenerator that supports batch fetching
|
||||
* sequence values.
|
||||
*/
|
||||
@Override
|
||||
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be,
|
||||
DataSource ds, String seqName, int batchSize) {
|
||||
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, int stepSize, String seqName) {
|
||||
|
||||
return new DB2SequenceIdGenerator(be, ds, seqName, batchSize);
|
||||
return new DB2SequenceIdGenerator(be, ds, seqName, sequenceBatchSize);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package io.ebean.config.dbplatform.db2;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.config.dbplatform.SequenceIdGenerator;
|
||||
import io.ebean.config.dbplatform.SequenceBatchIdGenerator;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
/**
|
||||
* DB2 specific sequence Id Generator.
|
||||
*/
|
||||
public class DB2SequenceIdGenerator extends SequenceIdGenerator {
|
||||
public class DB2SequenceIdGenerator extends SequenceBatchIdGenerator {
|
||||
|
||||
private final String baseSql;
|
||||
private final String unionBaseSql;
|
||||
|
||||
@@ -46,10 +46,8 @@ public class H2Platform extends DatabasePlatform {
|
||||
* sequence values.
|
||||
*/
|
||||
@Override
|
||||
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds,
|
||||
String seqName, int batchSize) {
|
||||
|
||||
return new H2SequenceIdGenerator(be, ds, seqName, batchSize);
|
||||
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, int stepSize, String seqName) {
|
||||
return new H2SequenceIdGenerator(be, ds, seqName, sequenceBatchSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package io.ebean.config.dbplatform.h2;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.config.dbplatform.SequenceIdGenerator;
|
||||
import io.ebean.config.dbplatform.SequenceBatchIdGenerator;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
/**
|
||||
* H2 specific sequence Id Generator.
|
||||
*/
|
||||
public class H2SequenceIdGenerator extends SequenceIdGenerator {
|
||||
public class H2SequenceIdGenerator extends SequenceBatchIdGenerator {
|
||||
|
||||
private final String baseSql;
|
||||
private final String unionBaseSql;
|
||||
|
||||
@@ -31,9 +31,8 @@ public class HsqldbPlatform extends DatabasePlatform {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
|
||||
|
||||
return new H2SequenceIdGenerator(be, ds, seqName, batchSize);
|
||||
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, int stepSize, String seqName) {
|
||||
return new H2SequenceIdGenerator(be, ds, seqName, sequenceBatchSize);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
package io.ebean.config.dbplatform.mysql;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.config.dbplatform.IdType;
|
||||
import io.ebean.config.dbplatform.PlatformIdGenerator;
|
||||
import io.ebean.config.dbplatform.SqlErrorCodes;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
@@ -38,6 +35,11 @@ public class MySqlPlatform extends DatabasePlatform {
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(true);
|
||||
this.dbIdentity.setSupportsIdentity(true);
|
||||
this.dbIdentity.setSupportsSequence(false);
|
||||
|
||||
this.dbDefaultValue.setNow("now(6)"); // must have same precision as TIMESTAMP
|
||||
this.dbDefaultValue.setFalse("0");
|
||||
this.dbDefaultValue.setTrue("1");
|
||||
|
||||
|
||||
this.exceptionTranslator =
|
||||
new SqlErrorCodes()
|
||||
@@ -49,14 +51,14 @@ public class MySqlPlatform extends DatabasePlatform {
|
||||
this.openQuote = "`";
|
||||
this.closeQuote = "`";
|
||||
// use pipe for escaping as it depends if mysql runs in no_backslash_escapes or not.
|
||||
this.likeClause = "like binary ? escape '|'";
|
||||
this.specialLikeCharacters = new char[] { '%', '_', '|' };
|
||||
this.likeClauseRaw = "like binary ? escape ''";
|
||||
this.likeClauseEscaped = "like binary ? escape '|'";
|
||||
|
||||
this.forwardOnlyHintOnFindIterate = true;
|
||||
this.booleanDbType = Types.BIT;
|
||||
|
||||
dbTypeMap.put(DbType.BIT, new DbPlatformType("tinyint(1) default 0"));
|
||||
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("tinyint(1) default 0"));
|
||||
dbTypeMap.put(DbType.BIT, new DbPlatformType("tinyint(1)"));
|
||||
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("tinyint(1)"));
|
||||
dbTypeMap.put(DbType.TIMESTAMP, new DbPlatformType("datetime(6)"));
|
||||
dbTypeMap.put(DbType.CLOB, new MySqlClob());
|
||||
dbTypeMap.put(DbType.BLOB, new MySqlBlob());
|
||||
@@ -64,24 +66,10 @@ public class MySqlPlatform extends DatabasePlatform {
|
||||
dbTypeMap.put(DbType.VARBINARY, new DbPlatformType("varbinary", 255));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return null in case there is a sequence annotation.
|
||||
*/
|
||||
@Override
|
||||
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be,
|
||||
DataSource ds, String seqName, int batchSize) {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode) {
|
||||
// NOWAIT and SKIP LOCKED currently not supported with MySQL
|
||||
return sql + " for update";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
|
||||
sb.append('|').append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,10 +35,12 @@ public class OraclePlatform extends DatabasePlatform {
|
||||
dbIdentity.setSupportsIdentity(true);
|
||||
dbIdentity.setSupportsGetGeneratedKeys(true);
|
||||
|
||||
this.treatEmptyStringsAsNull = true;
|
||||
this.dbDefaultValue.setFalse("0");
|
||||
this.dbDefaultValue.setTrue("1");
|
||||
this.dbDefaultValue.setNow("current_timestamp");
|
||||
|
||||
this.likeClause = "like ? escape '|'";
|
||||
this.specialLikeCharacters = new char[] { '%', '_', '|' };
|
||||
this.treatEmptyStringsAsNull = true;
|
||||
this.likeClauseRaw = "like ?";
|
||||
|
||||
this.exceptionTranslator =
|
||||
new SqlErrorCodes()
|
||||
@@ -51,7 +53,7 @@ public class OraclePlatform extends DatabasePlatform {
|
||||
this.closeQuote = "\"";
|
||||
|
||||
booleanDbType = Types.INTEGER;
|
||||
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("number(1) default 0"));
|
||||
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("number(1)"));
|
||||
|
||||
dbTypeMap.put(DbType.INTEGER, new DbPlatformType("number", 10));
|
||||
dbTypeMap.put(DbType.BIGINT, new DbPlatformType("number", 19));
|
||||
@@ -72,9 +74,8 @@ public class OraclePlatform extends DatabasePlatform {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
|
||||
|
||||
return new OracleSequenceIdGenerator(be, ds, seqName, batchSize);
|
||||
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, int stepSize, String seqName) {
|
||||
return new OracleSequenceIdGenerator(be, ds, seqName, sequenceBatchSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -89,8 +90,4 @@ public class OraclePlatform extends DatabasePlatform {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
|
||||
sb.append('|').append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package io.ebean.config.dbplatform.oracle;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.config.dbplatform.SequenceIdGenerator;
|
||||
import io.ebean.config.dbplatform.SequenceBatchIdGenerator;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
/**
|
||||
* Oracle specific sequence Id Generator.
|
||||
*/
|
||||
public class OracleSequenceIdGenerator extends SequenceIdGenerator {
|
||||
public class OracleSequenceIdGenerator extends SequenceBatchIdGenerator {
|
||||
|
||||
private final String baseSql;
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ public class PostgresPlatform extends DatabasePlatform {
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(true);
|
||||
this.dbIdentity.setSupportsSequence(true);
|
||||
|
||||
this.dbDefaultValue.setNow("current_timestamp");
|
||||
|
||||
this.exceptionTranslator =
|
||||
new SqlErrorCodes()
|
||||
.addAcquireLock("55P03")
|
||||
@@ -90,9 +92,9 @@ public class PostgresPlatform extends DatabasePlatform {
|
||||
* Create a Postgres specific sequence IdGenerator.
|
||||
*/
|
||||
@Override
|
||||
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
|
||||
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, int stepSize, String seqName) {
|
||||
|
||||
return new PostgresSequenceIdGenerator(be, ds, seqName, batchSize);
|
||||
return new PostgresSequenceIdGenerator(be, ds, seqName, sequenceBatchSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package io.ebean.config.dbplatform.postgres;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.config.dbplatform.SequenceIdGenerator;
|
||||
import io.ebean.config.dbplatform.SequenceBatchIdGenerator;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
/**
|
||||
* Postgres specific sequence Id Generator.
|
||||
*/
|
||||
public class PostgresSequenceIdGenerator extends SequenceIdGenerator {
|
||||
public class PostgresSequenceIdGenerator extends SequenceBatchIdGenerator {
|
||||
|
||||
private final String baseSql;
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ public class SqlAnywherePlatform extends DatabasePlatform {
|
||||
this.dbIdentity.setSelectLastInsertedIdTemplate("select @@IDENTITY as X");
|
||||
this.dbIdentity.setSupportsIdentity(true);
|
||||
|
||||
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("bit default 0"));
|
||||
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("bit"));
|
||||
dbTypeMap.put(DbType.BIGINT, new DbPlatformType("numeric", 19));
|
||||
dbTypeMap.put(DbType.REAL, new DbPlatformType("float(16)"));
|
||||
dbTypeMap.put(DbType.DOUBLE, new DbPlatformType("float(32)"));
|
||||
|
||||
@@ -19,12 +19,21 @@ public class SQLitePlatform extends DatabasePlatform {
|
||||
this.dbIdentity.setSelectLastInsertedIdTemplate("select last_insert_rowid()");
|
||||
|
||||
this.booleanDbType = Types.INTEGER;
|
||||
this.likeClauseRaw = "like ?";
|
||||
this.likeClauseEscaped = "like ?";
|
||||
this.dbDefaultValue.setFalse("0");
|
||||
this.dbDefaultValue.setTrue("1");
|
||||
this.dbDefaultValue.setNow("CURRENT_TIMESTAMP");
|
||||
|
||||
dbTypeMap.put(DbType.BIT, new DbPlatformType("int default 0"));
|
||||
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("int default 0"));
|
||||
dbTypeMap.put(DbType.BIT, new DbPlatformType("int"));
|
||||
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("int"));
|
||||
dbTypeMap.put(DbType.BIGINT, new DbPlatformType("integer"));
|
||||
dbTypeMap.put(DbType.SMALLINT, new DbPlatformType("integer"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
|
||||
sb.append(ch);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ public class SqlServerBasicSqlLimiter implements BasicSqlLimiter {
|
||||
StringBuilder sb = new StringBuilder(50 + dbSql.length());
|
||||
sb.append(dbSql);
|
||||
if (!dbSql.toLowerCase().contains("order by")) {
|
||||
sb.append(" order by 1 ");
|
||||
sb.append(" order by 1");
|
||||
}
|
||||
sb.append(" ").append("offset");
|
||||
sb.append(" ").append(firstRow).append(" rows");
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
package io.ebean.config.dbplatform.sqlserver;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.config.dbplatform.IdType;
|
||||
import io.ebean.config.dbplatform.PlatformIdGenerator;
|
||||
import io.ebean.config.dbplatform.SqlErrorCodes;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
@@ -18,8 +23,8 @@ public class SqlServerPlatform extends DatabasePlatform {
|
||||
public SqlServerPlatform() {
|
||||
super();
|
||||
this.platform = Platform.SQLSERVER;
|
||||
// effectively disable persistBatchOnCascade mode for SQL Server
|
||||
// due to lack of support for getGeneratedKeys in batch mode
|
||||
// disable persistBatchOnCascade mode for
|
||||
// SQL Server unless we are using sequences
|
||||
this.persistBatchOnCascade = PersistBatch.NONE;
|
||||
this.idInExpandedForm = true;
|
||||
this.selectCountWithAlias = true;
|
||||
@@ -29,21 +34,27 @@ public class SqlServerPlatform extends DatabasePlatform {
|
||||
this.dbIdentity.setIdType(IdType.IDENTITY);
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(true);
|
||||
this.dbIdentity.setSupportsIdentity(true);
|
||||
this.dbIdentity.setSupportsSequence(true);
|
||||
this.sequenceBatchMode = false;
|
||||
|
||||
this.exceptionTranslator =
|
||||
new SqlErrorCodes()
|
||||
.addAcquireLock("1222")
|
||||
.addDuplicateKey("2601", "2627")
|
||||
.addDataIntegrity("544", "8114", "8115")
|
||||
.addDataIntegrity("544", "547", "8114", "8115")
|
||||
.build();
|
||||
|
||||
this.openQuote = "[";
|
||||
this.closeQuote = "]";
|
||||
this.specialLikeCharacters = new char[]{'%', '_', '['};
|
||||
this.likeClause = "like ? COLLATE Latin1_General_BIN";
|
||||
this.likeSpecialCharacters = new char[]{'%', '_', '['};
|
||||
this.likeClauseRaw = "like ? collate Latin1_General_BIN";
|
||||
this.likeClauseEscaped = "like ? collate Latin1_General_BIN";
|
||||
|
||||
booleanDbType = Types.INTEGER;
|
||||
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("bit default 0"));
|
||||
this.dbDefaultValue.setFalse("0");
|
||||
this.dbDefaultValue.setTrue("1");
|
||||
this.dbDefaultValue.setNow("SYSUTCDATETIME()");
|
||||
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("bit"));
|
||||
|
||||
dbTypeMap.put(DbType.INTEGER, new DbPlatformType("integer", false));
|
||||
dbTypeMap.put(DbType.BIGINT, new DbPlatformType("numeric", 19));
|
||||
@@ -66,8 +77,45 @@ public class SqlServerPlatform extends DatabasePlatform {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(ServerConfig config) {
|
||||
super.configure(config);
|
||||
if (dbIdentity.getIdType() == IdType.SEQUENCE) {
|
||||
this.persistBatchOnCascade = PersistBatch.ALL;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
|
||||
sb.append('[').append(ch).append(']');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Postgres specific sequence IdGenerator.
|
||||
*/
|
||||
@Override
|
||||
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, int stepSize, String seqName) {
|
||||
return new SqlServerStepSequence(be, ds, seqName, stepSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* For update is part of the FROM clause on the base table for sql server.
|
||||
*/
|
||||
@Override
|
||||
public String fromForUpdate(Query.ForUpdate forUpdateMode) {
|
||||
switch (forUpdateMode) {
|
||||
case SKIPLOCKED:
|
||||
return "with (updlock,readpast)";
|
||||
case NOWAIT:
|
||||
return "with (updlock,nowait)";
|
||||
default:
|
||||
return "with (updlock)";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode) {
|
||||
// for update are hints on from clause of base table
|
||||
return sql;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package io.ebean.config.dbplatform.sqlserver;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.config.dbplatform.SequenceStepIdGenerator;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
public class SqlServerStepSequence extends SequenceStepIdGenerator {
|
||||
|
||||
private final String nextSql;
|
||||
|
||||
/**
|
||||
* Construct where batchSize is the sequence step size.
|
||||
*/
|
||||
public SqlServerStepSequence(BackgroundExecutor be, DataSource ds, String seqName, int stepSize) {
|
||||
super(be, ds, seqName, stepSize);
|
||||
this.nextSql = "select next value for "+seqName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSql(int batchSize) {
|
||||
return nextSql;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package io.ebean.config.properties;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Manages the underlying map of properties we are gathering.
|
||||
*/
|
||||
class LoadContext {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(LoadContext.class);
|
||||
|
||||
/**
|
||||
* Map we are loading the properties into.
|
||||
*/
|
||||
private final Map<String, String> map = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* Names of resources/files that were loaded.
|
||||
*/
|
||||
private final Set<String> loadedResources = new LinkedHashSet<>();
|
||||
|
||||
/**
|
||||
* Return the input stream (maybe null) for the given source.
|
||||
*/
|
||||
InputStream resource(String resourcePath, Loader.Source source) {
|
||||
|
||||
InputStream is = null;
|
||||
if (source == Loader.Source.RESOURCE) {
|
||||
is = getClass().getResourceAsStream("/" + resourcePath);
|
||||
if (is != null) {
|
||||
loadedResources.add(resourcePath);
|
||||
}
|
||||
} else {
|
||||
File file = new File(resourcePath);
|
||||
if (file.exists()) {
|
||||
try {
|
||||
is = new FileInputStream(file);
|
||||
loadedResources.add("file:"+resourcePath);
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return is;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a property entry.
|
||||
*/
|
||||
void put(String key, String val) {
|
||||
if (val != null) {
|
||||
val = val.trim();
|
||||
}
|
||||
map.put(key, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate all the expressions and return as a Properties object.
|
||||
*/
|
||||
Properties eval() {
|
||||
|
||||
log.info("loaded properties from {}", loadedResources);
|
||||
|
||||
Properties properties = new Properties();
|
||||
|
||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String value = PropertyEval.eval(entry.getValue());
|
||||
properties.setProperty(key, value);
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the special properties that can point to an external properties source.
|
||||
*/
|
||||
String indirectLocation() {
|
||||
String indirectLocation = map.get("load.properties");
|
||||
if (indirectLocation == null) {
|
||||
indirectLocation = map.get("load.properties.override");
|
||||
}
|
||||
return indirectLocation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package io.ebean.config.properties;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Loads the configuration from known/expected locations.
|
||||
* <p>
|
||||
* Defines the loading order of resources and files.
|
||||
* </p>
|
||||
*/
|
||||
class Loader {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(Loader.class);
|
||||
|
||||
enum Source {
|
||||
RESOURCE,
|
||||
FILE
|
||||
}
|
||||
|
||||
private final LoadContext loadContext = new LoadContext();
|
||||
|
||||
private YamlLoader yamlLoader;
|
||||
|
||||
Loader() {
|
||||
String skipYaml = System.getProperty("ebeanSkipYaml");
|
||||
if (!"true".equals(skipYaml)) {
|
||||
try {
|
||||
Class<?> exists = Class.forName("org.yaml.snakeyaml.Yaml");
|
||||
if (exists != null) {
|
||||
yamlLoader = new YamlLoader(loadContext);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the configuration with the expected ordering.
|
||||
*/
|
||||
void load() {
|
||||
|
||||
loadMain(Source.RESOURCE);
|
||||
// external file configuration overrides the resources configuration
|
||||
loadMain(Source.FILE);
|
||||
|
||||
loadViaSystemProperty();
|
||||
loadViaIndirection();
|
||||
|
||||
// test configuration (if found) overrides main configuration
|
||||
// we should only find these resources when running tests
|
||||
loadTest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load test configuration.
|
||||
*/
|
||||
private void loadTest() {
|
||||
loadProperties("application-test.properties", Source.RESOURCE);
|
||||
loadYaml("application-test.yml", Source.RESOURCE);
|
||||
loadProperties("test-ebean.properties", Source.RESOURCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load configuration defined by a <em>load.properties</em> entry in properties file.
|
||||
*/
|
||||
private void loadViaIndirection() {
|
||||
|
||||
String location = loadContext.indirectLocation();
|
||||
if (location != null) {
|
||||
location = PropertyEval.eval(location);
|
||||
loadWithExtensionCheck(location);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the main configuration for the given source.
|
||||
*/
|
||||
private void loadMain(Source source) {
|
||||
loadYaml("application.yml", source);
|
||||
loadProperties("application.properties", source);
|
||||
loadProperties("ebean.properties", source);
|
||||
}
|
||||
|
||||
private void loadViaSystemProperty() {
|
||||
String fileName = System.getenv("EBEAN_PROPS_FILE");
|
||||
if (fileName == null) {
|
||||
fileName = System.getProperty("ebean.props.file");
|
||||
if (fileName != null) {
|
||||
loadWithExtensionCheck(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loadWithExtensionCheck(String fileName) {
|
||||
if (fileName.endsWith("yml")) {
|
||||
loadYaml(fileName, Source.FILE);
|
||||
} else if (fileName.endsWith("properties")) {
|
||||
loadProperties(fileName, Source.FILE);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Expecting only yml or properties file but got [" + fileName + "]");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate all the configuration entries and return as properties.
|
||||
*/
|
||||
Properties eval() {
|
||||
return loadContext.eval();
|
||||
}
|
||||
|
||||
void loadYaml(String resourcePath, Source source) {
|
||||
if (yamlLoader != null) {
|
||||
try {
|
||||
yamlLoader.load(resource(resourcePath, source));
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to read yml from:" + resourcePath, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loadProperties(String resourcePath, Source source) {
|
||||
InputStream is = resource(resourcePath, source);
|
||||
if (is != null) {
|
||||
loadProperties(is);
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream resource(String resourcePath, Source source) {
|
||||
return loadContext.resource(resourcePath, source);
|
||||
}
|
||||
|
||||
private void loadProperties(InputStream is) {
|
||||
|
||||
if (is != null) {
|
||||
try {
|
||||
Properties properties = new Properties();
|
||||
properties.load(is);
|
||||
put(properties);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to load properties?", e);
|
||||
} finally {
|
||||
close(is);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void put(Properties properties) {
|
||||
Enumeration<?> enumeration = properties.propertyNames();
|
||||
while (enumeration.hasMoreElements()) {
|
||||
String key = (String) enumeration.nextElement();
|
||||
String property = properties.getProperty(key);
|
||||
loadContext.put(key, property);
|
||||
}
|
||||
}
|
||||
|
||||
private void close(InputStream is) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
log.warn("Error closing input stream for properties", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package io.ebean.config.properties;
|
||||
|
||||
import java.util.Enumeration;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Loads and evaluates properties and yml configuration.
|
||||
*/
|
||||
public class PropertiesLoader {
|
||||
|
||||
private static Properties properties;
|
||||
|
||||
/**
|
||||
* Provides properties by reading known locations.
|
||||
* <p>
|
||||
* <h3>Main configuration</h3>
|
||||
* <p>
|
||||
* <p>Firstly loads from main resources</p>
|
||||
* <pre>
|
||||
* - application.properties
|
||||
* - application.yml
|
||||
* - ebean.properties
|
||||
* </pre>
|
||||
* <p>
|
||||
* <p>Then loads from local files</p>
|
||||
* <pre>
|
||||
* - application.properties
|
||||
* - application.yml
|
||||
* - ebean.properties
|
||||
* </pre>
|
||||
* <p>
|
||||
* <p>Then loads from environment variable <em>EBEAN_PROPS_FILE</em></p>
|
||||
* <p>Then loads from system property <em>ebean.props.file</em></p>
|
||||
* <p>Then loads from <em>load.properties</em></p>
|
||||
* <p>
|
||||
* <h3>Test configuration</h3>
|
||||
* <p>
|
||||
* Once the main configuration is read it will try to read common test configuration.
|
||||
* This will only be successful if the test resources are available (i.e. running tests).
|
||||
* </p>
|
||||
* <p>Loads from test resources</p>
|
||||
* <pre>
|
||||
* - application-test.properties
|
||||
* - application-test.yml
|
||||
* - test-ebean.properties
|
||||
* </pre>
|
||||
*/
|
||||
public static synchronized Properties load() {
|
||||
|
||||
if (properties == null) {
|
||||
Loader loader = new Loader();
|
||||
loader.load();
|
||||
properties = loader.eval();
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set (override) a configuration property.
|
||||
*
|
||||
* This is expected to be only called by test configuration logic
|
||||
* (ala automatic test configuration via ebean-test-config).
|
||||
*/
|
||||
public static synchronized void setProperty(String key, String value) {
|
||||
load();
|
||||
properties.setProperty(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a copy of the properties with 'eval' run on all the values.
|
||||
* This resolves expressions like ${HOME} etc.
|
||||
*/
|
||||
public static Properties eval(Properties properties) {
|
||||
Properties evalCopy = new Properties();
|
||||
|
||||
Enumeration<?> names = properties.propertyNames();
|
||||
while (names.hasMoreElements()) {
|
||||
String name = (String)names.nextElement();
|
||||
String value = PropertyEval.eval(properties.getProperty(name));
|
||||
evalCopy.setProperty(name, value);
|
||||
}
|
||||
return evalCopy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
package io.ebean.config.properties;
|
||||
|
||||
import javax.naming.InitialContext;
|
||||
import javax.naming.NamingException;
|
||||
|
||||
/**
|
||||
* Helper used to evaluate expressions such as ${CATALINA_HOME}.
|
||||
* <p>
|
||||
* The expressions can contain environment variables, system properties or JNDI
|
||||
* properties. JNDI expressions take the form ${jndi:propertyName} where you
|
||||
* substitute propertyName with the name of the jndi property you wish to
|
||||
* evaluate.
|
||||
* </p>
|
||||
*/
|
||||
final class PropertyEval {
|
||||
|
||||
/**
|
||||
* Prefix for looking up JNDI Environment variable.
|
||||
*/
|
||||
private static final String JAVA_COMP_ENV = "java:comp/env/";
|
||||
|
||||
/**
|
||||
* Used to detect the start of an expression.
|
||||
*/
|
||||
private static final String START = "${";
|
||||
|
||||
/**
|
||||
* Used to detect the end of an expression.
|
||||
*/
|
||||
private static final String END = "}";
|
||||
|
||||
/**
|
||||
* Specify the PropertyHolder.
|
||||
*/
|
||||
private PropertyEval() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property value evaluating and replacing any expressions such as
|
||||
* ${CATALINA_HOME}.
|
||||
*/
|
||||
static String eval(String val) {
|
||||
if (val == null) {
|
||||
return null;
|
||||
}
|
||||
int sp = val.indexOf(START);
|
||||
if (sp > -1) {
|
||||
int ep = val.indexOf(END, sp + 1);
|
||||
if (ep > -1) {
|
||||
return eval(val, sp, ep);
|
||||
}
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the expression using JNDI, Environment variables, System Properties
|
||||
* or existing an property in SystemProperties itself.
|
||||
*/
|
||||
private static String evaluateExpression(String exp) {
|
||||
|
||||
if (isJndiExpression(exp)) {
|
||||
// JNDI property lookup...
|
||||
String val = getJndiProperty(exp);
|
||||
if (val != null) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
String val = System.getProperty(exp);
|
||||
if (val == null) {
|
||||
val = System.getenv(exp);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
private static String eval(String val, int sp, int ep) {
|
||||
|
||||
return new EvalBuffer(val, sp, ep).process();
|
||||
}
|
||||
|
||||
|
||||
private static boolean isJndiExpression(String exp) {
|
||||
return exp.startsWith("JNDI:") || exp.startsWith("jndi:");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null if JNDI is not setup or if the property is not found.
|
||||
*
|
||||
* @param key the key of the JNDI Environment property including a JNDI: prefix.
|
||||
*/
|
||||
private static String getJndiProperty(String key) {
|
||||
|
||||
try {
|
||||
// remove the JNDI: prefix
|
||||
key = key.substring(5);
|
||||
return (String) getJndiObject(key);
|
||||
} catch (NamingException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to getProperty but throws NamingException if JNDI is not setup or
|
||||
* if the property is not found.
|
||||
*/
|
||||
private static Object getJndiObject(String key) throws NamingException {
|
||||
|
||||
InitialContext ctx = new InitialContext();
|
||||
return ctx.lookup(JAVA_COMP_ENV + key);
|
||||
}
|
||||
|
||||
|
||||
private static class EvalBuffer {
|
||||
|
||||
private final StringBuilder buf = new StringBuilder();
|
||||
private final String original;
|
||||
private int position;
|
||||
private int start;
|
||||
private int end;
|
||||
|
||||
private String expression;
|
||||
private String defaultValue;
|
||||
|
||||
EvalBuffer(String val, int start, int ep) {
|
||||
this.original = val;
|
||||
this.start = start;
|
||||
this.end = ep;
|
||||
this.position = 0;
|
||||
moveToStart();
|
||||
}
|
||||
|
||||
void moveToStart() {
|
||||
if (start > position) {
|
||||
buf.append(original.substring(position, start));
|
||||
position = start;
|
||||
}
|
||||
}
|
||||
|
||||
void parseForDefault() {
|
||||
int colonPos = original.lastIndexOf(':', end);
|
||||
if (colonPos > start) {
|
||||
expression = original.substring(start + START.length(), colonPos);
|
||||
defaultValue = original.substring(colonPos + 1, end);
|
||||
} else {
|
||||
expression = original.substring(start + START.length(), end);
|
||||
}
|
||||
}
|
||||
|
||||
void evaluate() {
|
||||
String eval = evaluateExpression(expression);
|
||||
if (eval != null) {
|
||||
buf.append(eval);
|
||||
} else {
|
||||
if (defaultValue != null) {
|
||||
buf.append(defaultValue);
|
||||
} else {
|
||||
buf.append(START).append(expression).append(END);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String end() {
|
||||
if (end < original.length() - 1) {
|
||||
buf.append(original.substring(end + 1));
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
boolean next() {
|
||||
if (end < original.length()) {
|
||||
int startPos = original.indexOf(START, end + 1);
|
||||
if (startPos > -1) {
|
||||
int endPos = original.indexOf(END, startPos + 1);
|
||||
if (endPos > -1) {
|
||||
if (startPos > end + 1) {
|
||||
buf.append(original.substring(end + 1, startPos));
|
||||
}
|
||||
this.start = startPos;
|
||||
this.end = endPos;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void evalNext() {
|
||||
parseForDefault();
|
||||
evaluate();
|
||||
}
|
||||
|
||||
String process() {
|
||||
evalNext();
|
||||
while (next()) {
|
||||
evalNext();
|
||||
}
|
||||
return end();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package io.ebean.config.properties;
|
||||
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Loads configuration from Yml into the load context.
|
||||
* <p>
|
||||
* Note that this ignores 'lists' so just reads 'maps' and scalar values.
|
||||
* </p>
|
||||
*/
|
||||
class YamlLoader {
|
||||
|
||||
private final Yaml yaml = new Yaml();
|
||||
|
||||
private final LoadContext loadContext;
|
||||
|
||||
YamlLoader(LoadContext loadContext) {
|
||||
this.loadContext = loadContext;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
void load(InputStream is) {
|
||||
if (is != null) {
|
||||
for (Object map : yaml.loadAll(is)) {
|
||||
loadMap((Map<String, Object>)map, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
void loadMap(Map<String, Object> map, String path) {
|
||||
|
||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
if (path != null) {
|
||||
key = path + "." + key;
|
||||
}
|
||||
Object val = entry.getValue();
|
||||
if (val instanceof Map) {
|
||||
loadMap((Map<String, Object>) val, key);
|
||||
} else {
|
||||
addScalar(key, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addScalar(String key, Object val) {
|
||||
if (val instanceof String) {
|
||||
loadContext.put(key, (String) val);
|
||||
} else if (val instanceof Number || val instanceof Boolean) {
|
||||
loadContext.put(key, val.toString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -102,6 +102,14 @@ public interface DbMigration {
|
||||
*/
|
||||
void addPlatform(Platform platform, String prefix);
|
||||
|
||||
/**
|
||||
* Add an additional databasePlatform to write the migration DDL.
|
||||
* <p>
|
||||
* Use this when you want to add preconfigured database platforms.
|
||||
* </p>
|
||||
*/
|
||||
void addDatabasePlatform(DatabasePlatform databasePlatform, String prefix);
|
||||
|
||||
/**
|
||||
* Generate the next migration xml file and associated apply and rollback sql scripts.
|
||||
* <p>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.ebean.event;
|
||||
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.Transaction;
|
||||
|
||||
/**
|
||||
* A request to delete a bean by Id value.
|
||||
*/
|
||||
public interface BeanDeleteIdRequest {
|
||||
|
||||
/**
|
||||
* Return the server processing the request.
|
||||
*/
|
||||
EbeanServer getEbeanServer();
|
||||
|
||||
/**
|
||||
* Return the Transaction associated with this request.
|
||||
*/
|
||||
Transaction getTransaction();
|
||||
|
||||
/**
|
||||
* Returns the Id value of the bean being deleted.
|
||||
*/
|
||||
Object getId();
|
||||
|
||||
}
|
||||
@@ -87,4 +87,11 @@ public abstract class BeanPersistAdapter implements BeanPersistController {
|
||||
public void postSoftDelete(BeanPersistRequest<?> request) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Does nothing by default.
|
||||
*/
|
||||
@Override
|
||||
public void preDelete(BeanDeleteIdRequest request) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +92,11 @@ public interface BeanPersistController {
|
||||
*/
|
||||
boolean preSoftDelete(BeanPersistRequest<?> request);
|
||||
|
||||
/**
|
||||
* Prior to a delete by id perform some action.
|
||||
*/
|
||||
void preDelete(BeanDeleteIdRequest request);
|
||||
|
||||
/**
|
||||
* Called after the insert was performed.
|
||||
*/
|
||||
|
||||
@@ -12,6 +12,11 @@ public interface MetaInfoManager {
|
||||
*/
|
||||
List<MetaTimedMetric> collectTransactionStatistics(boolean reset);
|
||||
|
||||
/**
|
||||
* Collect query plan statistics (new, will migrate ORM query stats over to this).
|
||||
*/
|
||||
List<MetaQueryMetric> collectQueryStatistics(boolean reset);
|
||||
|
||||
/**
|
||||
* Collect and return the non-empty query plan statistics for all the beans.
|
||||
* <p>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package io.ebean.meta;
|
||||
|
||||
/**
|
||||
* Query execution metrics.
|
||||
*/
|
||||
public interface MetaQueryMetric extends MetaTimedMetric {
|
||||
|
||||
/**
|
||||
* The type of entity or DTO bean.
|
||||
*/
|
||||
Class<?> getType();
|
||||
|
||||
/**
|
||||
* The label for the query (can be null).
|
||||
*/
|
||||
String getLabel();
|
||||
|
||||
/**
|
||||
* The actual SQL of the query.
|
||||
*/
|
||||
String getSql();
|
||||
|
||||
}
|
||||
@@ -27,17 +27,24 @@ public interface MetaTimedMetric {
|
||||
long getCount();
|
||||
|
||||
/**
|
||||
* Return the total execution time.
|
||||
* Return the total execution time in micros.
|
||||
*/
|
||||
long getTotal();
|
||||
|
||||
/**
|
||||
* Return the max execution time.
|
||||
* Return the max execution time in micros.
|
||||
*/
|
||||
long getMax();
|
||||
|
||||
/**
|
||||
* Return the mean execution time.
|
||||
* Return the mean execution time in micros.
|
||||
*/
|
||||
long getMean();
|
||||
|
||||
/**
|
||||
* Return the total beans or rows processed or loaded.
|
||||
*
|
||||
* This will be 0 if the metric isn't a query plan (like transaction execution statistics).
|
||||
*/
|
||||
long getBeanCount();
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ package io.ebean.service;
|
||||
|
||||
import io.ebean.RawSql;
|
||||
import io.ebean.RawSqlBuilder;
|
||||
import io.ebean.SqlRow;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Service provided by Ebean for parsing and column mapping raw SQL queries.
|
||||
@@ -24,4 +26,13 @@ public interface SpiRawSqlService {
|
||||
* Unparsed SQL so explicit column mapping expected.
|
||||
*/
|
||||
RawSqlBuilder unparsed(String sql);
|
||||
|
||||
/**
|
||||
* Create based on a JDBC ResultSet.
|
||||
*
|
||||
* @param resultSet The ResultSet row to read as a SqlRow
|
||||
* @param dbTrueValue The DB true value
|
||||
* @param binaryOptimizedUUID Flag set to true if the UUID value is stored as optimised binary(16)
|
||||
*/
|
||||
SqlRow sqlRow(ResultSet resultSet, String dbTrueValue, boolean binaryOptimizedUUID) throws SQLException;
|
||||
}
|
||||
|
||||
@@ -136,7 +136,6 @@ public class EJson {
|
||||
/**
|
||||
* Parse the json returning as a List taking into account the current token.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> List<T> parseList(JsonParser parser, JsonToken currentToken) throws IOException {
|
||||
return plugin.parseList(parser, currentToken);
|
||||
}
|
||||
|
||||
@@ -34,13 +34,14 @@ public class AnnotationUtil {
|
||||
* does not execute specialized search algorithms for classes or methods. It only traverses through Annotations!
|
||||
* It also does not filter out platform dependent annotations!
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <A extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) {
|
||||
if (annotationType == null) {
|
||||
return null;
|
||||
}
|
||||
// check if directly present, if not, start search for meta-annotations.
|
||||
Annotation[] anns = annotatedElement.getAnnotations();
|
||||
if (anns.length == 0) {
|
||||
Annotation[] anns = annotatedElement.getAnnotations();
|
||||
if (anns.length == 0) {
|
||||
return null; // no annotations present, so searching for meta annotations not required
|
||||
}
|
||||
|
||||
@@ -51,7 +52,7 @@ public class AnnotationUtil {
|
||||
return (A) ann;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return findAnnotation(anns, annotationType, new HashSet<>());
|
||||
|
||||
}
|
||||
@@ -62,6 +63,7 @@ public class AnnotationUtil {
|
||||
* the supplied element.
|
||||
* <p><strong>Note</strong>: this method searches for annotations at class & superClass(es)!
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <A extends Annotation> A findAnnotationRecursive(Class<?> clazz, Class<A> annotationType) {
|
||||
if (annotationType == null) {
|
||||
return null;
|
||||
@@ -102,7 +104,7 @@ public class AnnotationUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all annotations recusively for a class and its superclasses.
|
||||
* Finds all annotations recusively for a class and its superclasses or interfaces.
|
||||
*/
|
||||
public static <A extends Annotation> Set<A> findAnnotationsRecursive(Class<?> clazz, Class<A> annotationType) {
|
||||
if (annotationType == null) {
|
||||
@@ -110,13 +112,28 @@ public class AnnotationUtil {
|
||||
}
|
||||
Set<A> ret = new LinkedHashSet<>();
|
||||
Set<Annotation> visited = new HashSet<>();
|
||||
while (clazz != null && clazz != Object.class) {
|
||||
findMetaAnnotations(clazz, annotationType, ret, visited);
|
||||
Set<Class<?>> visitedInterfaces = new HashSet<>();
|
||||
while (clazz != null && !clazz.getName().startsWith("java.lang.")) {
|
||||
findMetaAnnotationsRecursive(clazz, annotationType, ret, visited, visitedInterfaces);
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches the interfaces for annotations.
|
||||
*/
|
||||
private static <A extends Annotation> void findMetaAnnotationsRecursive(Class<?> clazz,
|
||||
Class<A> annotationType, Set<A> ret,
|
||||
Set<Annotation> visited, Set<Class<?>> visitedInterfaces) {
|
||||
findMetaAnnotations(clazz, annotationType, ret, visited);
|
||||
for (Class<?> iface : clazz.getInterfaces()) {
|
||||
if (!iface.getName().startsWith("java.lang.") && visitedInterfaces.add(iface)) {
|
||||
findMetaAnnotationsRecursive(iface, annotationType, ret, visited, visitedInterfaces);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the search algorithm avoiding endless recursion by tracking which
|
||||
* annotations have already been visited.
|
||||
|
||||
+29
-1
@@ -1,10 +1,12 @@
|
||||
package io.ebeaninternal.util;
|
||||
package io.ebean.util;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
/**
|
||||
* Utility for closing raw Jdbc resources.
|
||||
@@ -13,6 +15,32 @@ public class JdbcClose {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(JdbcClose.class);
|
||||
|
||||
/**
|
||||
* Close the resultSet logging if an error occurs.
|
||||
*/
|
||||
public static void close(Statement statement) {
|
||||
try {
|
||||
if (statement != null) {
|
||||
statement.close();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.warn("Error closing statement", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the resultSet logging if an error occurs.
|
||||
*/
|
||||
public static void close(ResultSet resultSet) {
|
||||
try {
|
||||
if (resultSet != null) {
|
||||
resultSet.close();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.warn("Error closing resultSet", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the connection logging if an error occurs.
|
||||
*/
|
||||
@@ -89,6 +89,7 @@ public class ScopeTrans {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ScopeTrans[" + transaction + "]";
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ public class ScopedTransaction extends SpiTransactionProxy {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ScopedTransaction[" + current + "]";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.DtoQuery;
|
||||
import io.ebeaninternal.server.dto.DtoMappingRequest;
|
||||
import io.ebeaninternal.server.dto.DtoQueryPlan;
|
||||
|
||||
/**
|
||||
* Internal extension to DtoQuery.
|
||||
*/
|
||||
public interface SpiDtoQuery<T> extends DtoQuery<T>, SpiSqlBinding {
|
||||
|
||||
/**
|
||||
* Return the key for query plan.
|
||||
*/
|
||||
String planKey();
|
||||
|
||||
/**
|
||||
* Get the query plan for the cache.
|
||||
*/
|
||||
DtoQueryPlan getQueryPlan(Object planKey);
|
||||
|
||||
/**
|
||||
* Build the query plan.
|
||||
*/
|
||||
DtoQueryPlan buildPlan(DtoMappingRequest request);
|
||||
|
||||
/**
|
||||
* Put the query plan into the cache.
|
||||
*/
|
||||
void putQueryPlan(Object planKey, DtoQueryPlan plan);
|
||||
|
||||
/**
|
||||
* Return true if the query is in relaxed mapping mode.
|
||||
*/
|
||||
boolean isRelaxedMode();
|
||||
|
||||
/**
|
||||
* Return the label for the query.
|
||||
*/
|
||||
String getLabel();
|
||||
|
||||
/**
|
||||
* Return the associated DTO bean type.
|
||||
*/
|
||||
Class<T> getType();
|
||||
|
||||
/**
|
||||
* Return an underlying ORM query (if this query is built from an ORM query).
|
||||
*/
|
||||
SpiQuery<?> getOrmQuery();
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.DtoQuery;
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.PersistenceContextScope;
|
||||
import io.ebean.Query;
|
||||
@@ -14,12 +15,15 @@ import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.event.readaudit.ReadAuditLogger;
|
||||
import io.ebean.event.readaudit.ReadAuditPrepare;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlHandler;
|
||||
import io.ebeaninternal.server.core.SpiResultSet;
|
||||
import io.ebeaninternal.server.core.timezone.DataTimeZone;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.query.CQuery;
|
||||
import io.ebeaninternal.server.transaction.RemoteTransactionEvent;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Service Provider extension to EbeanServer.
|
||||
@@ -224,4 +228,33 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
|
||||
*/
|
||||
void scopedTransactionExit(Object returnOrThrowable, int opCode);
|
||||
|
||||
/**
|
||||
* DTO findList query.
|
||||
*/
|
||||
<T> List<T> findDtoList(SpiDtoQuery<T> query);
|
||||
|
||||
/**
|
||||
* DTO findOne query.
|
||||
*/
|
||||
<T> T findDtoOne(SpiDtoQuery<T> query);
|
||||
|
||||
/**
|
||||
* DTO findEach query.
|
||||
*/
|
||||
<T> void findDtoEach(SpiDtoQuery<T> query, Consumer<T> consumer);
|
||||
|
||||
/**
|
||||
* DTO findEachWhile query.
|
||||
*/
|
||||
<T> void findDtoEachWhile(SpiDtoQuery<T> query, Predicate<T> consumer);
|
||||
|
||||
/**
|
||||
* Return / wrap the ORM query as a DTO query.
|
||||
*/
|
||||
<D> DtoQuery<D> findDto(Class<D> dtoType, SpiQuery<?> ormQuery);
|
||||
|
||||
/**
|
||||
* Execute the underlying ORM query returning as a JDBC ResultSet to map to DTO beans.
|
||||
*/
|
||||
SpiResultSet findResultSet(SpiQuery<?> ormQuery, SpiTransaction transaction);
|
||||
}
|
||||
|
||||
@@ -101,5 +101,5 @@ public interface SpiExpression extends Expression {
|
||||
/**
|
||||
* Check for match to a natural key query returning false if it doesn't match.
|
||||
*/
|
||||
boolean naturalKey(NaturalKeyQueryData data);
|
||||
boolean naturalKey(NaturalKeyQueryData<?> data);
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ public interface SpiExpressionRequest {
|
||||
/**
|
||||
* Append a DB Like clause.
|
||||
*/
|
||||
void appendLike();
|
||||
void appendLike(boolean rawLikeExpression);
|
||||
|
||||
/**
|
||||
* Escapes a string to use it as exact match in Like clause.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.CacheMode;
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.CountDistinctOrder;
|
||||
import io.ebean.ExpressionList;
|
||||
import io.ebean.OrderBy;
|
||||
import io.ebean.PersistenceContextScope;
|
||||
@@ -10,10 +10,10 @@ import io.ebean.Query;
|
||||
import io.ebean.bean.CallStack;
|
||||
import io.ebean.bean.ObjectGraphNode;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebean.event.BeanQueryRequest;
|
||||
import io.ebean.event.readaudit.ReadEvent;
|
||||
import io.ebean.plugin.BeanType;
|
||||
import io.ebeaninternal.server.autotune.ProfilingListener;
|
||||
import io.ebeaninternal.server.core.SpiOrmQueryRequest;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
|
||||
import io.ebeaninternal.server.deploy.TableJoin;
|
||||
@@ -215,6 +215,11 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
*/
|
||||
BeanDescriptor<T> getBeanDescriptor();
|
||||
|
||||
/**
|
||||
* Return the query plan key.
|
||||
*/
|
||||
Object getQueryPlanKey();
|
||||
|
||||
/**
|
||||
* Return the RawSql that was set to use for this query.
|
||||
*/
|
||||
@@ -338,7 +343,7 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
/**
|
||||
* Return a copy of the query attaching to a different EbeanServer.
|
||||
*/
|
||||
SpiQuery<T> copy(EbeanServer server);
|
||||
SpiQuery<T> copy(SpiEbeanServer server);
|
||||
|
||||
/**
|
||||
* Return the type of query (List, Set, Map, Bean, rowCount etc).
|
||||
@@ -567,7 +572,7 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
* The query plan excludes actual bind values (as they don't effect the query plan).
|
||||
* </p>
|
||||
*/
|
||||
CQueryPlanKey prepare(BeanQueryRequest<?> request);
|
||||
CQueryPlanKey prepare(SpiOrmQueryRequest<T> request);
|
||||
|
||||
/**
|
||||
* Calculate a hash based on the bind values used in the query.
|
||||
@@ -588,6 +593,16 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
*/
|
||||
boolean isRawSql();
|
||||
|
||||
/**
|
||||
* Return true if the query should have an order by appended automatically.
|
||||
*/
|
||||
boolean checkPagingOrderBy();
|
||||
|
||||
/**
|
||||
* Return true if there is no Order By clause.
|
||||
*/
|
||||
boolean orderByIsEmpty();
|
||||
|
||||
/**
|
||||
* Return the Order By clause or null if there is none defined.
|
||||
*/
|
||||
@@ -738,6 +753,16 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
*/
|
||||
boolean isDistinct();
|
||||
|
||||
/**
|
||||
* Return true if the Id property is manually included in the query (DTO queries).
|
||||
*/
|
||||
boolean isManualId();
|
||||
|
||||
/**
|
||||
* Set to true when we only include the Id property if it is explicitly included in the select().
|
||||
*/
|
||||
void setManualId(boolean manualId);
|
||||
|
||||
/**
|
||||
* Set default select clauses where none have been explicitly defined.
|
||||
*/
|
||||
@@ -813,4 +838,9 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
* Simplify nested expression lists where possible.
|
||||
*/
|
||||
void simplifyExpressions();
|
||||
|
||||
/**
|
||||
* Returns the count distinct order setting.
|
||||
*/
|
||||
CountDistinctOrder getCountDistinctOrder();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
/**
|
||||
* SQL query binding (for SqlQuery and DtoQuery).
|
||||
*/
|
||||
public interface SpiSqlBinding {
|
||||
|
||||
/**
|
||||
* Return the named or positioned parameters.
|
||||
*/
|
||||
BindParams getBindParams();
|
||||
|
||||
/**
|
||||
* return the query.
|
||||
*/
|
||||
String getQuery();
|
||||
|
||||
/**
|
||||
* Return the first row to fetch.
|
||||
*/
|
||||
int getFirstRow();
|
||||
|
||||
/**
|
||||
* Return the maximum number of rows to fetch.
|
||||
*/
|
||||
int getMaxRows();
|
||||
|
||||
/**
|
||||
* Return the query timeout.
|
||||
*/
|
||||
int getTimeout();
|
||||
|
||||
/**
|
||||
* Return the hint for Statement.setFetchSize().
|
||||
*/
|
||||
int getBufferFetchSizeHint();
|
||||
|
||||
}
|
||||
@@ -5,36 +5,6 @@ import io.ebean.SqlQuery;
|
||||
/**
|
||||
* SQL query - Internal extension to SqlQuery.
|
||||
*/
|
||||
public interface SpiSqlQuery extends SqlQuery {
|
||||
|
||||
/**
|
||||
* Return the named or positioned parameters.
|
||||
*/
|
||||
BindParams getBindParams();
|
||||
|
||||
/**
|
||||
* return the query.
|
||||
*/
|
||||
String getQuery();
|
||||
|
||||
/**
|
||||
* Return the first row to fetch.
|
||||
*/
|
||||
int getFirstRow();
|
||||
|
||||
/**
|
||||
* Return the maximum number of rows to fetch.
|
||||
*/
|
||||
int getMaxRows();
|
||||
|
||||
/**
|
||||
* Return the query timeout.
|
||||
*/
|
||||
int getTimeout();
|
||||
|
||||
/**
|
||||
* Return the hint for Statement.setFetchSize().
|
||||
*/
|
||||
int getBufferFetchSizeHint();
|
||||
public interface SpiSqlQuery extends SqlQuery, SpiSqlBinding {
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,23 @@ import io.ebean.SqlUpdate;
|
||||
|
||||
public interface SpiSqlUpdate extends SqlUpdate {
|
||||
|
||||
/**
|
||||
* Return the Bind parameters.
|
||||
*/
|
||||
BindParams getBindParams();
|
||||
|
||||
/**
|
||||
* Set the final sql being executed with named parameters replaced etc.
|
||||
*/
|
||||
void setGeneratedSql(String sql);
|
||||
|
||||
/**
|
||||
* Return true if we are using getGeneratedKeys.
|
||||
*/
|
||||
boolean isGetGeneratedKeys();
|
||||
|
||||
/**
|
||||
* Set the generated key value.
|
||||
*/
|
||||
void setGeneratedKey(Object idValue);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ public interface SpiTransactionManager {
|
||||
/**
|
||||
* Return the currently active transaction (can be null).
|
||||
*/
|
||||
SpiTransaction get();
|
||||
SpiTransaction getActive();
|
||||
|
||||
/**
|
||||
* Push an externally managed transaction into scope (e.g. Spring managed transaction).
|
||||
|
||||
@@ -47,7 +47,7 @@ public class DdlGenerator {
|
||||
this.jaxbPresent = serverConfig.getClassLoadConfig().isJavaxJAXBPresent();
|
||||
this.generateDdl = serverConfig.isDdlGenerate();
|
||||
this.createOnly = serverConfig.isDdlCreateOnly();
|
||||
if (serverConfig.getTenantMode().isDynamicDataSource() && serverConfig.isDdlRun()) {
|
||||
if (!serverConfig.getTenantMode().isDdlEnabled() && serverConfig.isDdlRun()) {
|
||||
log.warn("DDL can't be run on startup with TenantMode " + serverConfig.getTenantMode());
|
||||
this.runDdl = false;
|
||||
} else {
|
||||
@@ -127,6 +127,14 @@ public class DdlGenerator {
|
||||
|
||||
protected void runDropSql() throws IOException {
|
||||
if (!createOnly) {
|
||||
String ignoreExtraDdl = System.getProperty("ebean.ignoreExtraDdl");
|
||||
if (!"true".equalsIgnoreCase(ignoreExtraDdl) && jaxbPresent) {
|
||||
String extraApply = ExtraDdlXmlReader.buildExtra(server.getDatabasePlatform().getName(), true);
|
||||
if (extraApply != null) {
|
||||
runScript(false, extraApply, "extra-dll");
|
||||
}
|
||||
}
|
||||
|
||||
if (dropAllContent == null) {
|
||||
dropAllContent = readFile(getDropFileName());
|
||||
}
|
||||
@@ -142,7 +150,7 @@ public class DdlGenerator {
|
||||
|
||||
String ignoreExtraDdl = System.getProperty("ebean.ignoreExtraDdl");
|
||||
if (!"true".equalsIgnoreCase(ignoreExtraDdl) && jaxbPresent) {
|
||||
String extraApply = ExtraDdlXmlReader.buildExtra(server.getDatabasePlatform().getName());
|
||||
String extraApply = ExtraDdlXmlReader.buildExtra(server.getDatabasePlatform().getName(), false);
|
||||
if (extraApply != null) {
|
||||
runScript(false, extraApply, "extra-dll");
|
||||
}
|
||||
|
||||
@@ -183,6 +183,11 @@ public class DefaultDbMigration implements DbMigration {
|
||||
platforms.add(new Pair(getPlatform(platform), prefix));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDatabasePlatform(DatabasePlatform databasePlatform, String prefix) {
|
||||
platforms.add(new Pair(databasePlatform, prefix));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the next migration xml file and associated apply and rollback sql scripts.
|
||||
* <p>
|
||||
@@ -263,7 +268,7 @@ public class DefaultDbMigration implements DbMigration {
|
||||
if (extraDdl != null) {
|
||||
List<DdlScript> ddlScript = extraDdl.getDdlScript();
|
||||
for (DdlScript script : ddlScript) {
|
||||
if (ExtraDdlXmlReader.matchPlatform(dbPlatform.getName(), script.getPlatforms())) {
|
||||
if (!script.isDrop() && ExtraDdlXmlReader.matchPlatform(dbPlatform.getName(), script.getPlatforms())) {
|
||||
writeExtraDdl(migrationDir, script);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@ import io.ebeaninternal.dbmigration.ddlgeneration.platform.PlatformDdl;
|
||||
import io.ebeaninternal.dbmigration.migration.AddColumn;
|
||||
import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
|
||||
import io.ebeaninternal.dbmigration.migration.AddTableComment;
|
||||
import io.ebeaninternal.dbmigration.migration.AddUniqueConstraint;
|
||||
import io.ebeaninternal.dbmigration.migration.AlterColumn;
|
||||
import io.ebeaninternal.dbmigration.migration.AlterForeignKey;
|
||||
import io.ebeaninternal.dbmigration.migration.ChangeSet;
|
||||
import io.ebeaninternal.dbmigration.migration.CreateIndex;
|
||||
import io.ebeaninternal.dbmigration.migration.CreateTable;
|
||||
@@ -59,6 +61,10 @@ public class BaseDdlHandler implements DdlHandler {
|
||||
generate(writer, (AddHistoryTable) change);
|
||||
} else if (change instanceof DropHistoryTable) {
|
||||
generate(writer, (DropHistoryTable) change);
|
||||
} else if (change instanceof AddUniqueConstraint) {
|
||||
generate(writer, (AddUniqueConstraint) change);
|
||||
} else if (change instanceof AlterForeignKey) {
|
||||
generate(writer, (AlterForeignKey) change);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported change: " + change);
|
||||
}
|
||||
@@ -66,8 +72,13 @@ public class BaseDdlHandler implements DdlHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateExtra(DdlWrite write) throws IOException {
|
||||
tableDdl.generateExtra(write);
|
||||
public void generateProlog(DdlWrite write) throws IOException {
|
||||
tableDdl.generateProlog(write);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateEpilog(DdlWrite write) throws IOException {
|
||||
tableDdl.generateEpilog(write);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -120,4 +131,13 @@ public class BaseDdlHandler implements DdlHandler {
|
||||
tableDdl.generate(writer, dropIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(DdlWrite writer, AddUniqueConstraint constraint) throws IOException {
|
||||
tableDdl.generate(writer, constraint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(DdlWrite writer, AlterForeignKey alterForeignKey) throws IOException {
|
||||
tableDdl.generate(writer, alterForeignKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ package io.ebeaninternal.dbmigration.ddlgeneration;
|
||||
import io.ebeaninternal.dbmigration.migration.AddColumn;
|
||||
import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
|
||||
import io.ebeaninternal.dbmigration.migration.AddTableComment;
|
||||
import io.ebeaninternal.dbmigration.migration.AddUniqueConstraint;
|
||||
import io.ebeaninternal.dbmigration.migration.AlterColumn;
|
||||
import io.ebeaninternal.dbmigration.migration.AlterForeignKey;
|
||||
import io.ebeaninternal.dbmigration.migration.ChangeSet;
|
||||
import io.ebeaninternal.dbmigration.migration.CreateIndex;
|
||||
import io.ebeaninternal.dbmigration.migration.CreateTable;
|
||||
@@ -41,5 +43,11 @@ public interface DdlHandler {
|
||||
|
||||
void generate(DdlWrite writer, DropIndex dropIndex) throws IOException;
|
||||
|
||||
void generateExtra(DdlWrite write) throws IOException;
|
||||
void generate(DdlWrite writer, AddUniqueConstraint constraint) throws IOException;
|
||||
|
||||
void generate(DdlWrite writer, AlterForeignKey alterForeignKey) throws IOException;
|
||||
|
||||
void generateProlog(DdlWrite write) throws IOException;
|
||||
|
||||
void generateEpilog(DdlWrite write) throws IOException;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ package io.ebeaninternal.dbmigration.ddlgeneration;
|
||||
import io.ebeaninternal.dbmigration.migration.AddColumn;
|
||||
import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
|
||||
import io.ebeaninternal.dbmigration.migration.AddTableComment;
|
||||
import io.ebeaninternal.dbmigration.migration.AddUniqueConstraint;
|
||||
import io.ebeaninternal.dbmigration.migration.AlterColumn;
|
||||
import io.ebeaninternal.dbmigration.migration.AlterForeignKey;
|
||||
import io.ebeaninternal.dbmigration.migration.CreateIndex;
|
||||
import io.ebeaninternal.dbmigration.migration.CreateTable;
|
||||
import io.ebeaninternal.dbmigration.migration.DropColumn;
|
||||
@@ -68,8 +70,23 @@ public interface TableDdl {
|
||||
*/
|
||||
void generate(DdlWrite writer, DropIndex dropIndex) throws IOException;
|
||||
|
||||
/**
|
||||
* Write add unique constraint.
|
||||
*/
|
||||
void generate(DdlWrite writer, AddUniqueConstraint constraint) throws IOException;
|
||||
|
||||
/**
|
||||
* Writes alter foreign key statements.
|
||||
*/
|
||||
void generate(DdlWrite writer, AlterForeignKey alterForeignKey) throws IOException;
|
||||
|
||||
/**
|
||||
* Generate any extra DDL such as stored procedures or TableValueParameters.
|
||||
*/
|
||||
void generateProlog(DdlWrite write) throws IOException;
|
||||
|
||||
/**
|
||||
* Generate any extra DDL such as regeneration of history triggers.
|
||||
*/
|
||||
void generateExtra(DdlWrite write) throws IOException;
|
||||
void generateEpilog(DdlWrite write) throws IOException;
|
||||
}
|
||||
|
||||
+71
-84
@@ -13,7 +13,9 @@ import io.ebeaninternal.dbmigration.ddlgeneration.platform.util.IndexSet;
|
||||
import io.ebeaninternal.dbmigration.migration.AddColumn;
|
||||
import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
|
||||
import io.ebeaninternal.dbmigration.migration.AddTableComment;
|
||||
import io.ebeaninternal.dbmigration.migration.AddUniqueConstraint;
|
||||
import io.ebeaninternal.dbmigration.migration.AlterColumn;
|
||||
import io.ebeaninternal.dbmigration.migration.AlterForeignKey;
|
||||
import io.ebeaninternal.dbmigration.migration.Column;
|
||||
import io.ebeaninternal.dbmigration.migration.CreateIndex;
|
||||
import io.ebeaninternal.dbmigration.migration.CreateTable;
|
||||
@@ -330,7 +332,7 @@ public class BaseTableDdl implements TableDdl {
|
||||
}
|
||||
String[] columnNames = {col.getName()};
|
||||
write.apply()
|
||||
.append(platformDdl.alterTableAddUniqueConstraint(tableName, uqName, columnNames, Boolean.TRUE.equals(col.isNotnull())))
|
||||
.append(platformDdl.alterTableAddUniqueConstraint(tableName, uqName, columnNames, Boolean.TRUE.equals(col.isNotnull()) ? null : columnNames))
|
||||
.endOfStatement();
|
||||
|
||||
write.dropAllForeignKeys()
|
||||
@@ -340,9 +342,10 @@ public class BaseTableDdl implements TableDdl {
|
||||
|
||||
for (UniqueConstraint constraint : externalCompoundUnique) {
|
||||
String uqName = constraint.getName();
|
||||
String[] columnNames = StringHelper.delimitedToArray(constraint.getColumnNames(), ",", false);
|
||||
String[] columnNames = SplitColumns.split(constraint.getColumnNames());
|
||||
String[] nullableColumns = SplitColumns.split(constraint.getNullableColumns());
|
||||
write.apply()
|
||||
.append(platformDdl.alterTableAddUniqueConstraint(tableName, uqName, columnNames, false)) // TODO: check if nullable
|
||||
.append(platformDdl.alterTableAddUniqueConstraint(tableName, uqName, columnNames, nullableColumns))
|
||||
.endOfStatement();
|
||||
|
||||
write.dropAllForeignKeys()
|
||||
@@ -389,14 +392,7 @@ public class BaseTableDdl implements TableDdl {
|
||||
|
||||
protected void writeInlineForeignKey(DdlWrite write, Column column) throws IOException {
|
||||
|
||||
String references = column.getReferences();
|
||||
int pos = references.lastIndexOf('.');
|
||||
if (pos == -1) {
|
||||
throw new IllegalStateException("Expecting period '.' character for table.column split but not found in [" + references + "]");
|
||||
}
|
||||
String refTableName = references.substring(0, pos);
|
||||
String refColumnName = references.substring(pos + 1);
|
||||
String fkConstraint = platformDdl.tableInlineForeignKey(new String[]{column.getName()}, refTableName, new String[]{refColumnName});
|
||||
String fkConstraint = platformDdl.tableInlineForeignKey(new WriteForeignKey(null, column));
|
||||
write.apply().append(",").newLine().append(" ").append(fkConstraint);
|
||||
}
|
||||
|
||||
@@ -404,11 +400,7 @@ public class BaseTableDdl implements TableDdl {
|
||||
|
||||
List<ForeignKey> foreignKey = createTable.getForeignKey();
|
||||
for (ForeignKey key : foreignKey) {
|
||||
String refTableName = key.getRefTableName();
|
||||
String[] cols = toColumnNamesSplit(key.getColumnNames());
|
||||
String[] refColumns = toColumnNamesSplit(key.getRefColumnNames());
|
||||
|
||||
String fkConstraint = platformDdl.tableInlineForeignKey(cols, refTableName, refColumns);
|
||||
String fkConstraint = platformDdl.tableInlineForeignKey(new WriteForeignKey(null, key));
|
||||
write.apply().append(",").newLine().append(" ").append(fkConstraint);
|
||||
}
|
||||
}
|
||||
@@ -433,61 +425,41 @@ public class BaseTableDdl implements TableDdl {
|
||||
|
||||
List<ForeignKey> foreignKey = createTable.getForeignKey();
|
||||
for (ForeignKey key : foreignKey) {
|
||||
|
||||
String refTableName = key.getRefTableName();
|
||||
String fkName = key.getName();
|
||||
String[] cols = toColumnNamesSplit(key.getColumnNames());
|
||||
String[] refColumns = toColumnNamesSplit(key.getRefColumnNames());
|
||||
|
||||
writeForeignKey(write, fkName, tableName, cols, refTableName, refColumns, key.getIndexName());
|
||||
writeForeignKey(write, new WriteForeignKey(tableName, key));
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeForeignKey(DdlWrite write, String tableName, Column column) throws IOException {
|
||||
|
||||
String fkName = column.getForeignKeyName();
|
||||
String references = column.getReferences();
|
||||
int pos = references.lastIndexOf('.');
|
||||
if (pos == -1) {
|
||||
throw new IllegalStateException("Expecting period '.' character for table.column split but not found in [" + references + "]");
|
||||
}
|
||||
String refTableName = references.substring(0, pos);
|
||||
String refColumnName = references.substring(pos + 1);
|
||||
|
||||
String[] cols = {column.getName()};
|
||||
String[] refCols = {refColumnName};
|
||||
|
||||
writeForeignKey(write, fkName, tableName, cols, refTableName, refCols, column.getForeignKeyIndex());
|
||||
writeForeignKey(write, new WriteForeignKey(tableName, column));
|
||||
}
|
||||
|
||||
protected void writeForeignKey(DdlWrite write, String fkName, String tableName, String[] columns, String refTable, String[] refColumns, String indexName) throws IOException {
|
||||
protected void writeForeignKey(DdlWrite write, WriteForeignKey request) throws IOException {
|
||||
|
||||
tableName = lowerTableName(tableName);
|
||||
DdlBuffer fkeyBuffer = write.applyForeignKeys();
|
||||
alterTableAddForeignKey(fkeyBuffer, fkName, tableName, columns, refTable, refColumns);
|
||||
|
||||
if (indexName != null) {
|
||||
String tableName = lowerTableName(request.table());
|
||||
if (request.indexName() != null) {
|
||||
// no matching unique constraint so add the index
|
||||
fkeyBuffer.append(platformDdl.createIndex(indexName, tableName, columns)).endOfStatement();
|
||||
fkeyBuffer.append(platformDdl.createIndex(request.indexName(), tableName, request.cols())).endOfStatement();
|
||||
}
|
||||
|
||||
alterTableAddForeignKey(fkeyBuffer, request);
|
||||
|
||||
fkeyBuffer.end();
|
||||
|
||||
write.dropAllForeignKeys()
|
||||
.append(platformDdl.alterTableDropForeignKey(tableName, fkName)).endOfStatement();
|
||||
.append(platformDdl.alterTableDropForeignKey(tableName, request.fkName())).endOfStatement();
|
||||
|
||||
if (indexName != null) {
|
||||
if (request.indexName() != null) {
|
||||
write.dropAllForeignKeys()
|
||||
.append(platformDdl.dropIndex(indexName, tableName)).endOfStatement();
|
||||
.append(platformDdl.dropIndex(request.indexName(), tableName)).endOfStatement();
|
||||
}
|
||||
|
||||
write.dropAllForeignKeys().end();
|
||||
|
||||
}
|
||||
|
||||
protected void alterTableAddForeignKey(DdlBuffer buffer, String fkName, String tableName, String[] columns, String refTable, String[] refColumns) throws IOException {
|
||||
protected void alterTableAddForeignKey(DdlBuffer buffer, WriteForeignKey request) throws IOException {
|
||||
|
||||
String fkConstraint = platformDdl.alterTableAddForeignKey(tableName, fkName, columns, refTable, refColumns);
|
||||
String fkConstraint = platformDdl.alterTableAddForeignKey(request);
|
||||
if (fkConstraint != null && !fkConstraint.isEmpty()) {
|
||||
buffer.append(fkConstraint).endOfStatement();
|
||||
}
|
||||
@@ -554,7 +526,7 @@ public class BaseTableDdl implements TableDdl {
|
||||
for (UniqueConstraint uniqueConstraint : uniqueConstraints) {
|
||||
if (inlineUniqueWhenNull) {
|
||||
String uqName = uniqueConstraint.getName();
|
||||
String[] columns = toColumnNamesSplit(uniqueConstraint.getColumnNames());
|
||||
String[] columns = SplitColumns.split(uniqueConstraint.getColumnNames());
|
||||
apply.append(",").newLine();
|
||||
apply.append(" constraint ").append(uqName).append(" unique");
|
||||
appendColumns(columns, apply);
|
||||
@@ -571,17 +543,14 @@ public class BaseTableDdl implements TableDdl {
|
||||
|
||||
boolean inlineUniqueWhenNullable = platformDdl.isInlineUniqueWhenNullable();
|
||||
|
||||
List<Column> columns = createTable.getColumn();
|
||||
List<Column> columns = new WriteUniqueConstraint(createTable.getColumn()).uniqueKeys();
|
||||
for (Column column : columns) {
|
||||
if (!Boolean.TRUE.equals(column.isPrimaryKey())
|
||||
&& (hasValue(column.getUnique()) || hasValue(column.getUniqueOneToOne()))) {
|
||||
if (Boolean.TRUE.equals(column.isNotnull()) || inlineUniqueWhenNullable) {
|
||||
// normal mechanism for adding unique constraint
|
||||
inlineUniqueConstraintSingle(apply, column);
|
||||
} else {
|
||||
// MsSqlServer & DB2 specific mechanism for adding unique constraints (that allow nulls)
|
||||
externalUnique.add(column);
|
||||
}
|
||||
if (Boolean.TRUE.equals(column.isNotnull()) || inlineUniqueWhenNullable) {
|
||||
// normal mechanism for adding unique constraint
|
||||
inlineUniqueConstraintSingle(apply, column);
|
||||
} else {
|
||||
// SqlServer & DB2 specific mechanism for adding unique constraints (that allow nulls)
|
||||
externalUnique.add(column);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -625,13 +594,6 @@ public class BaseTableDdl implements TableDdl {
|
||||
return cols;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return as an array of string column names.
|
||||
*/
|
||||
protected String[] toColumnNamesSplit(String columns) {
|
||||
return columns.split(",");
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the table lower case.
|
||||
*/
|
||||
@@ -662,7 +624,7 @@ public class BaseTableDdl implements TableDdl {
|
||||
@Override
|
||||
public void generate(DdlWrite writer, CreateIndex createIndex) throws IOException {
|
||||
|
||||
String[] cols = toColumnNamesSplit(createIndex.getColumns());
|
||||
String[] cols = SplitColumns.split(createIndex.getColumns());
|
||||
writer.apply()
|
||||
.append(platformDdl.createIndex(createIndex.getIndexName(), createIndex.getTableName(), cols))
|
||||
.endOfStatement();
|
||||
@@ -679,6 +641,38 @@ public class BaseTableDdl implements TableDdl {
|
||||
.append(platformDdl.dropIndex(dropIndex.getIndexName(), dropIndex.getTableName()))
|
||||
.endOfStatement();
|
||||
}
|
||||
@Override
|
||||
public void generate(DdlWrite writer, AddUniqueConstraint constraint) throws IOException {
|
||||
|
||||
if (DdlHelp.isDropConstraint(constraint.getColumnNames())) {
|
||||
String ddl = platformDdl.alterTableDropUniqueConstraint(constraint.getTableName(), constraint.getConstraintName());
|
||||
if (hasValue(ddl)) {
|
||||
writer.apply().append(ddl).endOfStatement();
|
||||
}
|
||||
} else {
|
||||
String[] cols = SplitColumns.split(constraint.getColumnNames());
|
||||
String[] nullableColumns = SplitColumns.split(constraint.getNullableColumns());
|
||||
String ddl = platformDdl.alterTableAddUniqueConstraint(constraint.getTableName(), constraint.getConstraintName(), cols, nullableColumns);
|
||||
if (hasValue(ddl)) {
|
||||
writer.apply().append(ddl).endOfStatement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(DdlWrite writer, AlterForeignKey alterForeignKey) throws IOException {
|
||||
if (DdlHelp.isDropForeignKey(alterForeignKey.getColumnNames())) {
|
||||
String ddl = platformDdl.alterTableDropForeignKey(alterForeignKey.getTableName(), alterForeignKey.getName());
|
||||
if (hasValue(ddl)) {
|
||||
writer.apply().append(ddl).endOfStatement();
|
||||
}
|
||||
} else {
|
||||
String ddl = platformDdl.alterTableAddForeignKey(new WriteForeignKey(alterForeignKey));
|
||||
if (hasValue(ddl)) {
|
||||
writer.apply().append(ddl).endOfStatement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add add history table DDL.
|
||||
@@ -696,14 +690,20 @@ public class BaseTableDdl implements TableDdl {
|
||||
platformDdl.dropHistoryTable(writer, dropHistoryTable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateProlog(DdlWrite write) throws IOException {
|
||||
platformDdl.generateProlog(write);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called at the end to generate additional ddl such as regenerate history triggers.
|
||||
*/
|
||||
@Override
|
||||
public void generateExtra(DdlWrite write) throws IOException {
|
||||
public void generateEpilog(DdlWrite write) throws IOException {
|
||||
for (HistoryTableUpdate update : this.regenerateHistoryTriggers.values()) {
|
||||
platformDdl.regenerateHistoryTriggers(write, update);
|
||||
}
|
||||
platformDdl.generateEpilog(write);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -931,22 +931,9 @@ public class BaseTableDdl implements TableDdl {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected void alterColumnAddForeignKey(DdlWrite writer, AlterColumn alterColumn) throws IOException {
|
||||
|
||||
String tableName = alterColumn.getTableName();
|
||||
String fkName = alterColumn.getForeignKeyName();
|
||||
String[] cols = {alterColumn.getColumnName()};
|
||||
String references = alterColumn.getReferences();
|
||||
int pos = references.lastIndexOf('.');
|
||||
if (pos == -1) {
|
||||
throw new IllegalStateException("Expecting period '.' character for table.column split but not found in [" + references + "]");
|
||||
}
|
||||
String refTableName = references.substring(0, pos);
|
||||
String refColumnName = references.substring(pos + 1);
|
||||
String[] refCols = {refColumnName};
|
||||
|
||||
alterTableAddForeignKey(writer.apply(), fkName, tableName, cols, refTableName, refCols);
|
||||
alterTableAddForeignKey(writer.apply(), new WriteForeignKey(alterColumn));
|
||||
}
|
||||
|
||||
protected void alterColumnDropForeignKey(DdlWrite writer, AlterColumn alter) throws IOException {
|
||||
@@ -979,7 +966,7 @@ public class BaseTableDdl implements TableDdl {
|
||||
String[] cols = {alter.getColumnName()};
|
||||
boolean notNull = alter.isNotnull() != null ? alter.isNotnull() : Boolean.TRUE.equals(alter.isNotnull());
|
||||
writer.apply()
|
||||
.append(platformDdl.alterTableAddUniqueConstraint(alter.getTableName(), uqName, cols, notNull))
|
||||
.append(platformDdl.alterTableAddUniqueConstraint(alter.getTableName(), uqName, cols, notNull ? null : cols))
|
||||
.endOfStatement();
|
||||
|
||||
writer.dropAllForeignKeys()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import io.ebean.annotation.ConstraintMode;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
|
||||
/**
|
||||
@@ -18,13 +19,18 @@ public class DB2Ddl extends PlatformDdl {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, boolean notNull) {
|
||||
if (notNull) {
|
||||
return super.alterTableAddUniqueConstraint(tableName, uqName, columns, true);
|
||||
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, String[] nullableColumns) {
|
||||
if (nullableColumns == null || nullableColumns.length == 0) {
|
||||
return super.alterTableAddUniqueConstraint(tableName, uqName, columns, nullableColumns);
|
||||
} else {
|
||||
// Hmm: Complex workaround: https://www.ibm.com/developerworks/mydeveloperworks/blogs/SQLTips4DB2LUW/entry/unique_where_not_null_indexes26?lang=en
|
||||
return "-- NOT SUPPORTED " + super.alterTableAddUniqueConstraint(tableName, uqName, columns, true);
|
||||
return "-- NOT SUPPORTED " + super.alterTableAddUniqueConstraint(tableName, uqName, columns, nullableColumns);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void appendForeignKeyOnUpdate(StringBuilder buffer, ConstraintMode mode) {
|
||||
// do nothing, no on update clause for db2
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,18 +5,35 @@ public class DdlHelp {
|
||||
|
||||
public static final String DROP_COMMENT = "DROP COMMENT";
|
||||
|
||||
/**
|
||||
* Return true if the default value is the special DROP DEFAULT value.
|
||||
*/
|
||||
public static boolean isDropDefault(String defaultValue) {
|
||||
return DROP_DEFAULT.equals(defaultValue);
|
||||
}
|
||||
public static final String DROP_CONSTRAINT = "DROP CONSTRAINT";
|
||||
|
||||
public static final String DROP_FOREIGN_KEY = "DROP FOREIGN KEY";
|
||||
|
||||
/**
|
||||
* Return true if the default value is the special DROP DEFAULT value.
|
||||
*/
|
||||
public static boolean isDropComment(String comment) {
|
||||
return DROP_COMMENT.equals(comment);
|
||||
public static boolean isDropDefault(String value) {
|
||||
return DROP_DEFAULT.equals(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the default value is the special DROP COMMENT value.
|
||||
*/
|
||||
public static boolean isDropComment(String value) {
|
||||
return DROP_COMMENT.equals(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the default value is the special DROP CONSTRAINT value.
|
||||
*/
|
||||
public static boolean isDropConstraint(String value) {
|
||||
return DROP_CONSTRAINT.equals(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the default value is the special DROP FOREIGN KEY value.
|
||||
*/
|
||||
public static boolean isDropForeignKey(String value) {
|
||||
return DROP_FOREIGN_KEY.equals(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public class MySqlDdl extends PlatformDdl {
|
||||
*/
|
||||
@Override
|
||||
public String dropIndex(String indexName, String tableName) {
|
||||
return "drop index " + indexName + " on " + tableName;
|
||||
return "drop index " + maxConstraintName(indexName) + " on " + tableName;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,7 +34,7 @@ public class MySqlDdl extends PlatformDdl {
|
||||
*/
|
||||
@Override
|
||||
public String alterTableDropForeignKey(String tableName, String fkName) {
|
||||
return "alter table " + tableName + " drop foreign key " + fkName;
|
||||
return "alter table " + tableName + " drop foreign key " + maxConstraintName(fkName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -108,6 +108,15 @@ public class MySqlDdl extends PlatformDdl {
|
||||
*/
|
||||
@Override
|
||||
public void addTableComment(DdlBuffer apply, String tableName, String tableComment) throws IOException {
|
||||
if (DdlHelp.isDropComment(tableComment)) {
|
||||
tableComment = "";
|
||||
}
|
||||
apply.append(String.format("alter table %s comment = '%s'", tableName, tableComment)).endOfStatement();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) throws IOException {
|
||||
// alter comment currently not supported as it requires to repeat whole column definition
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import io.ebean.annotation.ConstraintMode;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
|
||||
/**
|
||||
@@ -14,7 +15,6 @@ public class Oracle10Ddl extends PlatformDdl {
|
||||
this.dropConstraintIfExists = "drop constraint";
|
||||
this.dropIndexIfExists = "drop index ";
|
||||
this.dropTableCascade = " cascade constraints purge";
|
||||
this.foreignKeyRestrict = "";
|
||||
this.alterColumn = "modify";
|
||||
this.columnSetNotnull = "not null";
|
||||
this.columnSetNull = "null";
|
||||
@@ -22,4 +22,30 @@ public class Oracle10Ddl extends PlatformDdl {
|
||||
this.identitySuffix = " generated always as identity";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, String[] nullableColumns) {
|
||||
if (nullableColumns == null || nullableColumns.length == 0) {
|
||||
return super.alterTableAddUniqueConstraint(tableName, uqName, columns, nullableColumns);
|
||||
} else {
|
||||
// Hmm: https://stackoverflow.com/questions/11893134/oracle-create-unique-index-but-ignore-nulls
|
||||
return "-- NOT YET IMPLEMENTED: " + super.alterTableAddUniqueConstraint(tableName, uqName, columns, nullableColumns);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void appendForeignKeyOnUpdate(StringBuilder buffer, ConstraintMode mode) {
|
||||
// do nothing, no on update clause for oracle
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void appendForeignKeyMode(StringBuilder buffer, String onMode, ConstraintMode mode) {
|
||||
switch (mode) {
|
||||
case SET_NULL:
|
||||
case CASCADE:
|
||||
super.appendForeignKeyMode(buffer, onMode, mode);
|
||||
default:
|
||||
// do nothing, defaults to RESTRICT effectively
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+99
-44
@@ -1,23 +1,25 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import io.ebean.annotation.ConstraintMode;
|
||||
import io.ebean.config.DbConstraintNaming;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DbDefaultValue;
|
||||
import io.ebean.config.dbplatform.DbIdentity;
|
||||
import io.ebean.config.dbplatform.IdType;
|
||||
import io.ebean.util.StringHelper;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.BaseDdlHandler;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlHandler;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.util.PlatformTypeConverter;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.util.VowelRemover;
|
||||
import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
|
||||
import io.ebeaninternal.dbmigration.migration.AlterColumn;
|
||||
import io.ebeaninternal.dbmigration.migration.Column;
|
||||
import io.ebeaninternal.dbmigration.migration.DropHistoryTable;
|
||||
import io.ebeaninternal.dbmigration.migration.IdentityType;
|
||||
import io.ebeaninternal.dbmigration.model.MTable;
|
||||
import io.ebean.util.StringHelper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -62,7 +64,8 @@ public class PlatformDdl {
|
||||
*/
|
||||
protected String dropSequenceIfExists = "drop sequence if exists ";
|
||||
|
||||
protected String foreignKeyRestrict = "on delete restrict on update restrict";
|
||||
protected String foreignKeyOnDelete = "on delete";
|
||||
protected String foreignKeyOnUpdate = "on update";
|
||||
|
||||
protected String identitySuffix = " auto_increment";
|
||||
|
||||
@@ -211,7 +214,7 @@ public class PlatformDdl {
|
||||
buffer.append(" ");
|
||||
buffer.append(lowerColumnName(column.getName()), 29);
|
||||
buffer.append(platformType);
|
||||
if (!Boolean.TRUE.equals(column.isPrimaryKey()) && !typeContainsDefault(platformType)) {
|
||||
if (!Boolean.TRUE.equals(column.isPrimaryKey())) {
|
||||
String defaultValue = convertDefaultValue(column.getDefaultValue());
|
||||
if (defaultValue != null) {
|
||||
buffer.append(" default ").append(defaultValue);
|
||||
@@ -225,13 +228,6 @@ public class PlatformDdl {
|
||||
// so that the database can potentially provide a nice SQL error
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the type definition already contains a default value.
|
||||
*/
|
||||
private boolean typeContainsDefault(String platformType) {
|
||||
return platformType.toLowerCase().contains(" default");
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the DB column default literal to platform specific.
|
||||
*/
|
||||
@@ -243,7 +239,7 @@ public class PlatformDdl {
|
||||
* Return the drop foreign key clause.
|
||||
*/
|
||||
public String alterTableDropForeignKey(String tableName, String fkName) {
|
||||
return "alter table " + alterTableIfExists + tableName + " " + dropConstraintIfExists + " " + fkName;
|
||||
return "alter table " + alterTableIfExists + tableName + " " + dropConstraintIfExists + " " + maxConstraintName(fkName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -306,9 +302,7 @@ public class PlatformDdl {
|
||||
if (initialValue > 1) {
|
||||
sb.append(" start with ").append(initialValue);
|
||||
}
|
||||
if (allocationSize > 0 && allocationSize != 50) {
|
||||
// at this stage ignoring allocationSize 50 as this is the 'default' and
|
||||
// not consistent with the way Ebean batch fetches sequence values
|
||||
if (allocationSize > 1) {
|
||||
sb.append(" increment by ").append(allocationSize);
|
||||
}
|
||||
sb.append(";");
|
||||
@@ -333,7 +327,7 @@ public class PlatformDdl {
|
||||
* Return the drop index statement.
|
||||
*/
|
||||
public String dropIndex(String indexName, String tableName) {
|
||||
return dropIndexIfExists + indexName;
|
||||
return dropIndexIfExists + maxConstraintName(indexName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -342,7 +336,7 @@ public class PlatformDdl {
|
||||
public String createIndex(String indexName, String tableName, String[] columns) {
|
||||
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
buffer.append("create index ").append(indexName).append(" on ").append(tableName);
|
||||
buffer.append("create index ").append(maxConstraintName(indexName)).append(" on ").append(tableName);
|
||||
appendColumns(columns, buffer);
|
||||
|
||||
return buffer.toString();
|
||||
@@ -351,49 +345,80 @@ public class PlatformDdl {
|
||||
/**
|
||||
* Return the foreign key constraint when used inline with create table.
|
||||
*/
|
||||
public String tableInlineForeignKey(String[] columns, String refTable, String[] refColumns) {
|
||||
public String tableInlineForeignKey(WriteForeignKey request) {
|
||||
|
||||
StringBuilder buffer = new StringBuilder(90);
|
||||
buffer.append("foreign key");
|
||||
appendColumns(columns, buffer);
|
||||
buffer.append(" references ").append(lowerTableName(refTable));
|
||||
appendColumns(refColumns, buffer);
|
||||
appendWithSpace(foreignKeyRestrict, buffer);
|
||||
appendColumns(request.cols(), buffer);
|
||||
buffer.append(" references ").append(lowerTableName(request.refTable()));
|
||||
appendColumns(request.refCols(), buffer);
|
||||
appendForeignKeySuffix(request, buffer);
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add foreign key.
|
||||
*/
|
||||
public String alterTableAddForeignKey(String tableName, String fkName, String[] columns, String refTable, String[] refColumns) {
|
||||
public String alterTableAddForeignKey(WriteForeignKey request) {
|
||||
|
||||
StringBuilder buffer = new StringBuilder(90);
|
||||
buffer
|
||||
.append("alter table ").append(tableName)
|
||||
.append(" add constraint ").append(fkName)
|
||||
.append("alter table ").append(lowerTableName(request.table()))
|
||||
.append(" add constraint ").append(maxConstraintName(request.fkName()))
|
||||
.append(" foreign key");
|
||||
appendColumns(columns, buffer);
|
||||
appendColumns(request.cols(), buffer);
|
||||
buffer
|
||||
.append(" references ")
|
||||
.append(lowerTableName(refTable));
|
||||
appendColumns(refColumns, buffer);
|
||||
appendWithSpace(foreignKeyRestrict, buffer);
|
||||
|
||||
.append(lowerTableName(request.refTable()));
|
||||
appendColumns(request.refCols(), buffer);
|
||||
appendForeignKeySuffix(request, buffer);
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
protected void appendForeignKeySuffix(WriteForeignKey request, StringBuilder buffer) {
|
||||
appendForeignKeyOnDelete(buffer, withDefault(request.onDelete()));
|
||||
appendForeignKeyOnUpdate(buffer, withDefault(request.onDelete()));
|
||||
}
|
||||
|
||||
protected ConstraintMode withDefault(ConstraintMode mode) {
|
||||
return (mode == null) ? ConstraintMode.RESTRICT : mode;
|
||||
}
|
||||
|
||||
protected void appendForeignKeyOnDelete(StringBuilder buffer, ConstraintMode mode) {
|
||||
appendForeignKeyMode(buffer, foreignKeyOnDelete, mode);
|
||||
}
|
||||
|
||||
protected void appendForeignKeyOnUpdate(StringBuilder buffer, ConstraintMode mode) {
|
||||
appendForeignKeyMode(buffer, foreignKeyOnUpdate, mode);
|
||||
}
|
||||
|
||||
protected void appendForeignKeyMode(StringBuilder buffer, String onMode, ConstraintMode mode) {
|
||||
buffer.append(" ").append(onMode).append(" ").append(translate(mode));
|
||||
}
|
||||
|
||||
protected String translate(ConstraintMode mode) {
|
||||
switch(mode) {
|
||||
case SET_NULL: return "set null";
|
||||
case SET_DEFAULT: return "set default";
|
||||
case RESTRICT: return "restrict";
|
||||
case CASCADE: return "cascade";
|
||||
default:
|
||||
throw new IllegalStateException("Unknown mode "+mode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop a unique constraint from the table (Sometimes this is an index).
|
||||
*/
|
||||
public String alterTableDropUniqueConstraint(String tableName, String uniqueConstraintName) {
|
||||
return "alter table " + tableName + " " + dropUniqueConstraint + " " + uniqueConstraintName;
|
||||
return "alter table " + tableName + " " + dropUniqueConstraint + " " + maxConstraintName(uniqueConstraintName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop a unique constraint from the table.
|
||||
*/
|
||||
public String alterTableDropConstraint(String tableName, String constraintName) {
|
||||
return "alter table " + tableName + " " + dropConstraint + " " + constraintName;
|
||||
return "alter table " + tableName + " " + dropConstraint + " " + maxConstraintName(constraintName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -401,10 +426,10 @@ public class PlatformDdl {
|
||||
* <p>
|
||||
* Overridden by MsSqlServer for specific null handling on unique constraints.
|
||||
*/
|
||||
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, boolean notNull) {
|
||||
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, String[] nullableColumns) {
|
||||
|
||||
StringBuilder buffer = new StringBuilder(90);
|
||||
buffer.append("alter table ").append(tableName).append(" add constraint ").append(uqName).append(" unique ");
|
||||
buffer.append("alter table ").append(tableName).append(" add constraint ").append(maxConstraintName(uqName)).append(" unique ");
|
||||
appendColumns(columns, buffer);
|
||||
return buffer.toString();
|
||||
}
|
||||
@@ -418,17 +443,14 @@ public class PlatformDdl {
|
||||
.append(" ").append(convertedType);
|
||||
|
||||
if (!onHistoryTable) {
|
||||
if (isTrue(column.isNotnull())) {
|
||||
buffer.append(" not null");
|
||||
}
|
||||
|
||||
if (defaultValue != null) {
|
||||
if (typeContainsDefault(convertedType)) {
|
||||
logger.error("Cannot set default value for '" + tableName + "." + column.getName() + "'");
|
||||
} else {
|
||||
buffer.append(" default ");
|
||||
buffer.append(defaultValue);
|
||||
}
|
||||
buffer.append(" default ");
|
||||
buffer.append(defaultValue);
|
||||
}
|
||||
|
||||
if (isTrue(column.isNotnull())) {
|
||||
buffer.append(" not null");
|
||||
}
|
||||
buffer.endOfStatement();
|
||||
|
||||
@@ -485,14 +507,14 @@ public class PlatformDdl {
|
||||
*/
|
||||
public String alterTableAddCheckConstraint(String tableName, String checkConstraintName, String checkConstraint) {
|
||||
|
||||
return "alter table " + tableName + " " + addConstraint + " " + checkConstraintName + " " + checkConstraint;
|
||||
return "alter table " + tableName + " " + addConstraint + " " + maxConstraintName(checkConstraintName) + " " + checkConstraint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter column setting the default value.
|
||||
*/
|
||||
public String alterColumnDefaultValue(String tableName, String columnName, String defaultValue) {
|
||||
String suffix = DdlHelp.isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + defaultValue;
|
||||
String suffix = DdlHelp.isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + convertDefaultValue(defaultValue);
|
||||
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + suffix;
|
||||
}
|
||||
|
||||
@@ -586,4 +608,37 @@ public class PlatformDdl {
|
||||
}
|
||||
apply.append(String.format("comment on column %s.%s is '%s'", table, column, comment)).endOfStatement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to generate a prolog for each script (stored procedures)
|
||||
*/
|
||||
public void generateProlog(DdlWrite write) throws IOException {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to generate an epilog. Will be added at the end of script
|
||||
*/
|
||||
public void generateEpilog(DdlWrite write) throws IOException {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortens the given name to the maximum constraint name length of the platform in a deterministic way.
|
||||
*
|
||||
* First, all vowels are removed, If the string is still to long, 31 bits are taken from the hash code
|
||||
* of the string and base36 encoded (10 digits and 26 chars) string.
|
||||
*
|
||||
* As 36^6 > 31^2, the resulting string is never longer as 6 chars.
|
||||
*/
|
||||
protected String maxConstraintName(String name) {
|
||||
if (name.length() > platform.getMaxConstraintNameLength()) {
|
||||
int hash = name.hashCode() & 0x7FFFFFFF;
|
||||
name = VowelRemover.trim(name, 4);
|
||||
if (name.length() > platform.getMaxConstraintNameLength()) {
|
||||
return name.substring(0, platform.getMaxConstraintNameLength()-7) + "_" + Integer.toString(hash, 36);
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* DB2 platform specific DDL.
|
||||
*/
|
||||
@@ -17,17 +15,17 @@ public class SQLiteDdl extends PlatformDdl {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTableComment(DdlBuffer apply, String tableName, String tableComment) throws IOException {
|
||||
public void addTableComment(DdlBuffer apply, String tableName, String tableComment) {
|
||||
// not supported
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) throws IOException {
|
||||
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) {
|
||||
// not supported
|
||||
}
|
||||
|
||||
@Override
|
||||
public String alterTableAddForeignKey(String tableName, String fkName, String[] columns, String refTable, String[] refColumns) {
|
||||
public String alterTableAddForeignKey(WriteForeignKey request) {
|
||||
// not supported
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
public class SplitColumns {
|
||||
|
||||
/**
|
||||
* Return as an array of string column names.
|
||||
*/
|
||||
public static String[] split(String columns) {
|
||||
if (columns == null || columns.isEmpty()) {
|
||||
return new String[0];
|
||||
}
|
||||
return columns.split(",");
|
||||
}
|
||||
|
||||
}
|
||||
+18
-12
@@ -1,5 +1,6 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import io.ebean.annotation.ConstraintMode;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
|
||||
import io.ebeaninternal.dbmigration.migration.AlterColumn;
|
||||
@@ -14,7 +15,6 @@ public class SqlServerDdl extends PlatformDdl {
|
||||
public SqlServerDdl(DatabasePlatform platform) {
|
||||
super(platform);
|
||||
this.identitySuffix = " identity(1,1)";
|
||||
this.foreignKeyRestrict = "";
|
||||
this.alterTableIfExists = "";
|
||||
this.addColumn = "add";
|
||||
this.inlineUniqueWhenNullable = false;
|
||||
@@ -23,6 +23,13 @@ public class SqlServerDdl extends PlatformDdl {
|
||||
this.historyDdl = new SqlServerHistoryDdl();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void appendForeignKeyMode(StringBuilder buffer, String onMode, ConstraintMode mode) {
|
||||
if (mode != ConstraintMode.RESTRICT) {
|
||||
super.appendForeignKeyMode(buffer, onMode, mode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String dropTable(String tableName) {
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
@@ -36,7 +43,7 @@ public class SqlServerDdl extends PlatformDdl {
|
||||
@Override
|
||||
public String alterTableDropForeignKey(String tableName, String fkName) {
|
||||
int pos = tableName.lastIndexOf('.');
|
||||
String objectId = fkName;
|
||||
String objectId = maxConstraintName(fkName);
|
||||
if (pos != -1) {
|
||||
objectId = tableName.substring(0, pos + 1) + fkName;
|
||||
}
|
||||
@@ -50,15 +57,16 @@ public class SqlServerDdl extends PlatformDdl {
|
||||
|
||||
@Override
|
||||
public String dropIndex(String indexName, String tableName) {
|
||||
return "IF EXISTS (SELECT name FROM sys.indexes WHERE object_id = OBJECT_ID('" + tableName +"','U') AND name = '" + indexName + "') drop index " + indexName + " ON " + tableName;
|
||||
return "IF EXISTS (SELECT name FROM sys.indexes WHERE object_id = OBJECT_ID('" + tableName + "','U') AND name = '"
|
||||
+ maxConstraintName(indexName) + "') drop index " + maxConstraintName(indexName) + " ON " + tableName;
|
||||
}
|
||||
/**
|
||||
* MsSqlServer specific null handling on unique constraints.
|
||||
*/
|
||||
@Override
|
||||
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, boolean notNull) {
|
||||
if (notNull) {
|
||||
return super.alterTableAddUniqueConstraint(tableName, uqName, columns, notNull);
|
||||
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, String[] nullableColumns) {
|
||||
if (nullableColumns == null || nullableColumns.length == 0) {
|
||||
return super.alterTableAddUniqueConstraint(tableName, uqName, columns, nullableColumns);
|
||||
}
|
||||
if (uqName == null) {
|
||||
throw new NullPointerException();
|
||||
@@ -75,7 +83,7 @@ public class SqlServerDdl extends PlatformDdl {
|
||||
}
|
||||
sb.append(") where");
|
||||
String sep = " ";
|
||||
for (String column : columns) {
|
||||
for (String column : nullableColumns) {
|
||||
sb.append(sep).append(column).append(" is not null");
|
||||
sep = " and ";
|
||||
}
|
||||
@@ -95,7 +103,7 @@ public class SqlServerDdl extends PlatformDdl {
|
||||
@Override
|
||||
public String alterTableDropUniqueConstraint(String tableName, String uniqueConstraintName) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("IF (OBJECT_ID('").append(uniqueConstraintName).append("', 'UQ') IS NOT NULL) ");
|
||||
sb.append("IF (OBJECT_ID('").append(maxConstraintName(uniqueConstraintName)).append("', 'UQ') IS NOT NULL) ");
|
||||
sb.append(super.alterTableDropUniqueConstraint(tableName, uniqueConstraintName)).append(";\n");
|
||||
sb.append(dropIndex(uniqueConstraintName, tableName));
|
||||
return sb.toString();
|
||||
@@ -114,9 +122,7 @@ public class SqlServerDdl extends PlatformDdl {
|
||||
} else {
|
||||
sb.append(" start with 1 ");
|
||||
}
|
||||
if (allocationSize > 0 && allocationSize != 50) {
|
||||
// at this stage ignoring allocationSize 50 as this is the 'default' and
|
||||
// not consistent with the way Ebean batch fetches sequence values
|
||||
if (allocationSize > 1) {
|
||||
sb.append(" increment by ").append(allocationSize);
|
||||
}
|
||||
sb.append(";");
|
||||
@@ -140,7 +146,7 @@ public class SqlServerDdl extends PlatformDdl {
|
||||
sb.append("if @Tmp is not null EXEC('alter table ").append(tableName).append(" drop constraint ' + @Tmp)$$");
|
||||
} else {
|
||||
sb.append("alter table ").append(tableName);
|
||||
sb.append(" add default ").append(defaultValue).append(" for ").append(columnName);
|
||||
sb.append(" add default ").append(convertDefaultValue(defaultValue)).append(" for ").append(columnName);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import io.ebean.annotation.ConstraintMode;
|
||||
import io.ebeaninternal.dbmigration.migration.AlterColumn;
|
||||
import io.ebeaninternal.dbmigration.migration.AlterForeignKey;
|
||||
import io.ebeaninternal.dbmigration.migration.Column;
|
||||
import io.ebeaninternal.dbmigration.migration.ForeignKey;
|
||||
|
||||
class WriteForeignKey {
|
||||
|
||||
private final String fkName;
|
||||
private final String tableName;
|
||||
private final String[] cols;
|
||||
private String refTableName;
|
||||
private String[] refCols;
|
||||
private final String indexName;
|
||||
private final ConstraintMode onDelete;
|
||||
private final ConstraintMode onUpdate;
|
||||
|
||||
WriteForeignKey(AlterColumn alterColumn) {
|
||||
this.tableName = alterColumn.getTableName();
|
||||
this.indexName = alterColumn.getForeignKeyIndex();
|
||||
this.fkName = alterColumn.getForeignKeyName();
|
||||
this.cols = new String[]{alterColumn.getColumnName()};
|
||||
setReferences(alterColumn.getReferences());
|
||||
this.onDelete = modeOf(alterColumn.getForeignKeyOnDelete());
|
||||
this.onUpdate = modeOf(alterColumn.getForeignKeyOnUpdate());
|
||||
}
|
||||
|
||||
WriteForeignKey(String tableName, ForeignKey key) {
|
||||
this.tableName = tableName;
|
||||
this.indexName = key.getIndexName();
|
||||
this.fkName = key.getName();
|
||||
this.cols = toCols(key.getColumnNames());
|
||||
this.refTableName = key.getRefTableName();
|
||||
this.refCols = toCols(key.getRefColumnNames());
|
||||
this.onDelete = modeOf(key.getOnDelete());
|
||||
this.onUpdate = modeOf(key.getOnUpdate());
|
||||
}
|
||||
|
||||
WriteForeignKey(AlterForeignKey key) {
|
||||
this.tableName = key.getTableName();
|
||||
this.indexName = key.getIndexName();
|
||||
this.fkName = key.getName();
|
||||
this.cols = toCols(key.getColumnNames());
|
||||
this.refTableName = key.getRefTableName();
|
||||
this.refCols = toCols(key.getRefColumnNames());
|
||||
this.onDelete = modeOf(key.getOnDelete());
|
||||
this.onUpdate = modeOf(key.getOnUpdate());
|
||||
}
|
||||
|
||||
WriteForeignKey(String tableName, Column column) {
|
||||
this.tableName = tableName;
|
||||
this.indexName = column.getForeignKeyIndex();
|
||||
this.fkName = column.getForeignKeyName();
|
||||
this.cols = new String[]{column.getName()};
|
||||
setReferences(column.getReferences());
|
||||
this.onDelete = modeOf(column.getForeignKeyOnDelete());
|
||||
this.onUpdate = modeOf(column.getForeignKeyOnUpdate());
|
||||
}
|
||||
|
||||
private void setReferences(String references) {
|
||||
int pos = references.lastIndexOf('.');
|
||||
if (pos == -1) {
|
||||
throw new IllegalStateException("Expecting period '.' character for table.column split but not found in [" + references + "]");
|
||||
}
|
||||
this.refTableName = references.substring(0, pos);
|
||||
String refColumnName = references.substring(pos + 1);
|
||||
this.refCols = new String[]{refColumnName};
|
||||
}
|
||||
|
||||
private String[] toCols(String columns) {
|
||||
return SplitColumns.split(columns);
|
||||
}
|
||||
|
||||
private ConstraintMode modeOf(String value) {
|
||||
return (value == null) ? null : ConstraintMode.valueOf(value);
|
||||
}
|
||||
|
||||
public String table() {
|
||||
return tableName;
|
||||
}
|
||||
|
||||
public String[] cols() {
|
||||
return cols;
|
||||
}
|
||||
|
||||
public String indexName() {
|
||||
return indexName;
|
||||
}
|
||||
|
||||
public String fkName() {
|
||||
return fkName;
|
||||
}
|
||||
|
||||
public String refTable() {
|
||||
return refTableName;
|
||||
}
|
||||
|
||||
public String[] refCols() {
|
||||
return refCols;
|
||||
}
|
||||
|
||||
public ConstraintMode onDelete() {
|
||||
return onDelete;
|
||||
}
|
||||
|
||||
public ConstraintMode onUpdate() {
|
||||
return onUpdate;
|
||||
}
|
||||
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import io.ebeaninternal.dbmigration.migration.Column;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class WriteUniqueConstraint {
|
||||
|
||||
private final List<Column> primaryKeys = new ArrayList<>();
|
||||
private final List<Column> uniqueKeys = new ArrayList<>();
|
||||
|
||||
WriteUniqueConstraint(List<Column> columns) {
|
||||
|
||||
// filter for unique and primary keys
|
||||
for (Column column : columns) {
|
||||
if (Boolean.TRUE.equals(column.isPrimaryKey())) {
|
||||
primaryKeys.add(column);
|
||||
}
|
||||
if (hasValue(column.getUnique()) || hasValue(column.getUniqueOneToOne())){
|
||||
uniqueKeys.add(column);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if null or trimmed string is empty.
|
||||
*/
|
||||
boolean hasValue(String value) {
|
||||
return value != null && !value.trim().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the single columns with unique constraints (that are not the primary key).
|
||||
*/
|
||||
public List<Column> uniqueKeys() {
|
||||
if (uniqueKeys.isEmpty() || primaryKeys.size() > 1) {
|
||||
// all single column unique constraints are valid
|
||||
return uniqueKeys;
|
||||
}
|
||||
// filter out PFK
|
||||
for (Column primaryKey : primaryKeys) {
|
||||
uniqueKeys.remove(primaryKey);
|
||||
}
|
||||
return uniqueKeys;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user