Compare commits

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

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

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

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

* compile fixes

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

* Remove parameter (from other PR)

* FIX: Mac detection

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

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

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

* removed license header
2018-03-02 09:10:21 +13:00
Rob Bygrave 9e2ddc9f8c [maven-release-plugin] prepare for next development iteration 2018-03-01 13:43:27 +13:00
253 changed files with 9088 additions and 1250 deletions
+1
View File
@@ -10,3 +10,4 @@ indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
spaces_around_operators = true
max_line_length = 130
+1
View File
@@ -20,3 +20,4 @@ profiling/
*.ipr
*.iws
.idea/
*uuid.state
+298
View File
@@ -0,0 +1,298 @@
Overview of ebean.properties file
=================================
### DbMigration options
You can set individual values for different platforms when generating migrations
`dbmigration.platform.<PLATFORM>.databaseSequenceBatchSize`
For DB's using sequences this is the number of sequence values prefetched.
`dbmigration.platform.<PLATFORM>.dbuuid`
Control, how UUID generation should work - it affects DDL which column type is generated. Possible values:
- BINARY enforces binary UUID
- VARCHAR enforces varchar UUID
- BINARY_OPTIMIZED enforces binary-optimized UUID (makes sense only with Type1 ID)
- AUTO_BINARY use binary when platform does not support native UUID
- AUTO_BINARY_OPTIMIZED use binary-optimized when platform does not support native UUID
- AUTO_VARCHAR (default) use varchar when platform does not support UUID
`dbmigration.platform.<PLATFORM>.uuidStoreAsBinary`
Same as setting dbuuid to BINARY
`dbmigration.platform.<PLATFORM>.geometrySRID`
The Geometry SRID value (default 4326).
`dbmigration.platform.<PLATFORM>.idType`
The ID type (IDENTITY, SEQUENCE, GENERATOR, EXTERNAL)
`dbmigration.platform.<PLATFORM>.mapping`
Adjust the mapping. For example `BOOLEAN=integer(32);BIT=tinyint(3)`
`ebean.migration.applyPrefix`
Set this to "V" to be compatible with FlywayDB.
`ebean.migration.applySuffix`
Defaults to '.sql'
`ebean.migration.dbusername`
DB user used to run the DB migration.
`ebean.migration.dbpassword`
DB password used to run the DB migration.
`ebean.migration.generate`
Set to true if the DB migration should be generated on server start.
`ebean.migration.generatePendingDrop`
The version of a pending drop that should be generated as the next migration.
`ebean.migration.includeGeneratedFileComment`
TODO
`ebean.migration.metaTable`
For running migration the DB table that holds migration execution status. Default 'db_migration'
`ebean.migration.migrationPath`
Resource path for the migration xml and sql.
`ebean.migration.modelPath`
Subdirectory the model xml files go into. Default 'model'
`ebean.migration.modelSuffix`
Suffix. Default '.model.xml'
`ebean.migration.name`
Description text that can be appended to the version to become the ddl script file name
`ebean.migration.patchInsertOn`
migration versions that should be added to history without running.
`ebean.migration.patchResetChecksumOn`
migration versions that should have their checksum reset and not run.
Use this if you get a 'Checksum mismatch' error.
`ebean.migration.placeholders`
A comma and equals delimited placeholders that are substituted in SQL scripts when running migration (used by DB Migration runner only).
`ebean.migration.platform`
The database platform to generate migration DDL for.
`ebean.migration.run`
Flag set to true means to run any outstanding migrations on startup.
`ebean.migration.version`
The migration version name (typically FlywayDb compatible). Example: 1.1.1_2
### Ebean UUID options
`ebean.uuidVersion`
Controls, how the UUIDs are generated. Possible values
- VERSION4 (default) generate random V4 UUIDs,
- VERSION1 generate rfc4122 compliant Type 1 UUIDs (requires a state file)
- VERSION1RND generate fake Type 1 UUIDs
Note, that V1 UUIDs in conjunction with AUTO_BINARY_OPTIMIZED will give you the best index performance, but you MUST understand how this works to avoid collisions.
`ebean.uuidStateFile`
The state file that is Required to generate V1 UUIDs
### DocStoreConfig
`ebean.docstore.active`
True when the Document store integration is active/on.
`ebean.docstore.allowAllCertificates`
Set to true such that the client allows connections to invalid/self signed SSL certificates.
`ebean.docstore.bulkBatchSize`
The default batch size to use for the Bulk API calls.
`ebean.docstore.create`
When true the Document store should create any document indexes that don't already exist.
`ebean.docstore.dropCreate`
When true the Document store should drop and re-create document indexes.
`ebean.docstore.generateMapping`
Set to true means Ebean will generate mapping files on startup.
`ebean.docstore.mappingPath`
Resource path for the Document store mapping files.
`ebean.docstore.mappingSuffix`
Suffix used for mapping files.
`ebean.docstore.password`
Password credential that be used for authentication to document store.
`ebean.docstore.pathToResources`
Location of resources that mapping files are generated into.
`ebean.docstore.persist`
The default mode used by indexes. `DEFAULT|IGNORE|QUEUE|UPDATE`
`ebean.docstore.url`
The URL of the Document store server. For example: http://localhost:9200.
`ebean.docstore.username`
Credential that be used for authentication to document store.
### Ebean Autotune
`ebean.autoTune.mode`
The autoTune mode `DEFAULT_OFF|DEFAULT_ON|DEFAULT_ONIFEMPTY`
`ebean.autoTune.profiling`
TODO
`ebean.autoTune.profilingBase`
TODO
`ebean.autoTune.profilingFile`
TODO
`ebean.autoTune.profilingRate`
TODO
`ebean.autoTune.profilingUpdateFrequency`
TODO
`ebean.autoTune.queryTuning`
TODO
`ebean.autoTune.queryTuningAddVersion`
TODO
`ebean.autoTune.queryTuningFile`
TODO
`ebean.autoTune.skipGarbageCollectionOnShutdown`
TODO
`ebean.autoTune.skipProfileReportingOnShutdown`
TODO
### Ebean options
`ebean.allQuotedIdentifiers`
Quote all identifiers
`ebean.asOfSysPeriod`
Column used to support history and 'As of' queries. This column is a timestamp range or equivalent.
`ebean.asOfViewSuffix`
Suffix appended to the base table to derive the view that contains the union of the base table and the history table in order to support asOf queries.
`ebean.autoCommitMode`
Set to true if the DataSource uses autoCommit. Indicates that Ebean should use autoCommit friendly Transactions and TransactionManager.
`ebean.autoReadOnlyDataSource`
When true create a read only DataSource using readOnlyDataSourceConfig defaulting values from dataSourceConfig
`ebean.autostart`
Should the server start all
`ebean.backgroundExecutorSchedulePoolSize`
TODO
`ebean.backgroundExecutorShutdownSecs`
TODO
and some more todos:
`ebean.batch.mode`
`ebean.batch.size`
`ebean.changeLogAsync`
`ebean.changeLogIncludeInserts`
`ebean.classes`
`ebean.collectQueryOrigins`
`ebean.collectQueryStatsByNode`
`ebean.currentUserProvider`
`ebean.dataSourceJndiName`
`ebean.dataTimeZone`
`ebean.databaseBooleanFalse`
`ebean.databaseBooleanTrue`
`ebean.databasePlatform`
`ebean.databasePlatformName`
`ebean.databaseSequenceBatchSize`
ebean.datasource.h2.adminpassword
ebean.datasource.h2.adminusername
`ebean.dbEncrypt`
`ebean.dbOffline`
ebean.ddl.createOnly
ebean.ddl.generate
ebean.ddl.header
ebean.ddl.initSql
ebean.ddl.run
ebean.ddl.seedSql
ebean.defaultDeleteMissingChildren
ebean.defaultOrderById
ebean.disableClasspathSearch
ebean.disableL2Cache
ebean.docStoreOnly
ebean.docstore.active
ebean.docstore.allowAllCertificates
ebean.docstore.bulkBatchSize
ebean.docstore.create
ebean.docstore.dropCreate
ebean.docstore.generateMapping
ebean.docstore.mappingPath
ebean.docstore.mappingSuffix
ebean.docstore.password
ebean.docstore.pathToResources
ebean.docstore.persist
ebean.docstore.url
ebean.docstore.username
ebean.encryptDeployManager
ebean.encryptKeyManager
ebean.encryptor
ebean.explicitTransactionBeginMode
ebean.expressionEqualsWithNullAsNoop
ebean.expressionNativeIlike
ebean.geometrySRID
ebean.historyTableSuffix
ebean.jdbcFetchSizeFindEach
ebean.jdbcFetchSizeFindList
ebean.jodaLocalTimeMode
ebean.jsonDateTime
ebean.jsonInclude
ebean.lazyLoadBatchSize
ebean.localTimeWithNanos
ebean.namingConvention.schema
ebean.namingConvention.sequenceFormat
ebean.namingConvention.useForeignKeyPrefix
ebean.namingconvention
ebean.notifyL2CacheInForeground
ebean.packages
ebean.persistBatch
ebean.persistBatchOnCascade
ebean.persistBatchSize
ebean.persistBatching
ebean.persistenceContextScope
ebean.profiling
ebean.profiling.directory
ebean.profiling.includeProfileIds
ebean.profiling.minimumMicros
ebean.profiling.profilesPerFile
ebean.profiling.verbose
ebean.queryBatchSize
ebean.queryPlanTTLSeconds
ebean.search.packages
ebean.serverCachePlugin
ebean.skipCacheAfterWrite
ebean.slowQueryMillis
ebean.tenant.catalogProvider
ebean.tenant.currentTenantProvider
ebean.tenant.mode
ebean.tenant.partitionColumn
ebean.tenant.schemaProvider
ebean.updateAllPropertiesInBatch
ebean.updateChangesOnly
ebean.updatesDeleteMissingChildren
ebean.useJavaxValidationNotNull
ebean.useJtaTransactionManager
+3 -3
View File
@@ -9,7 +9,7 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.12.1</version>
<version>11.14.3</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.12.1</tag>
<tag>ebean-11.14.3</tag>
</scm>
<profiles>
@@ -136,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 -->
@@ -0,0 +1,27 @@
package io.ebean;
/**
* Enumeration to use with {@link Query#setCountDistinct(CountDistinctOrder)}.
* @author Roland Praml, FOCONIS AG
*
*/
public enum CountDistinctOrder {
NO_ORDERING,
/** order by attribute ascending */
ATTR_ASC,
/** order by attribute descending */
ATTR_DESC,
/** order by count ascending and attribute ascending */
COUNT_ASC_ATTR_ASC,
/** order by count ascending and attribute descending */
COUNT_ASC_ATTR_DESC,
/** order by count descending and attribute ascending */
COUNT_DESC_ATTR_ASC,
/** order by count descending and attribute descending */
COUNT_DESC_ATTR_DESC,
}
+33
View File
@@ -0,0 +1,33 @@
package io.ebean;
import java.io.Serializable;
/**
* Holds a distinct value with it's count.
* (Used with {@link Query#findSingleAttributeList()} and {@link Query#setCountDistinct(CountDistinctOrder)}.)
* @author Roland Praml, FOCONIS AG
*/
public class CountedValue<A> implements Serializable {
private static final long serialVersionUID = -2267971668356749695L;
private final A value;
private final long count;
public CountedValue(A value, long count) {
this.value = value;
this.count = count;
}
public long getCount() {
return count;
}
public A getValue() {
return value;
}
@Override
public String toString() {
return count + ": " + value;
}
}
+133
View File
@@ -0,0 +1,133 @@
package io.ebean;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* Query for performing native SQL queries that return DTO Bean's.
* <p>
* These beans are just normal classes. They must have public constructors
* and setters.
* <p>
* Constructors with arguments are used if the number of constructor arguments
* matches the number of columns in the resultSet.
* </p>
* <p>
* If the number of columns in the resultSet is greater than the largest constructor
* then the largest constructor is used for the first columns and remaining columns
* are mapped by setter methods.
* </p>
*
* <pre>{@code
*
* // CustomerDto is just a 'bean like' class
* // with public constructor(s) and public setter methods
*
* String sql = "select id, name from customer where name like :name and status_code = :status";
*
* List<CustomerDto> beans =
* Ebean.findDto(CustomrDto.class, sql)
* .setParameter("name", "Acme%")
* .setParameter("status", "ACTIVE")
* .findList();
*
* }</pre>
*/
public interface DtoQuery<T> {
/**
* Execute the query returning a list.
*/
@Nonnull
List<T> findList();
/**
* Execute the query iterating a row at a time.
* <p>
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
* </p>
*/
void findEach(Consumer<T> consumer);
/**
* Execute the query iterating a row at a time with the ability to stop consuming part way through.
* <p>
* Returning false after processing a row stops the iteration through the query results.
* </p>
* <p>
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
* </p>
*/
void findEachWhile(Predicate<T> consumer);
/**
* Execute the query returning a single bean.
*/
@Nullable
T findOne();
/**
* Execute the query returning an optional bean.
*/
@Nonnull
Optional<T> findOneOrEmpty();
/**
* The same as bind for named parameters.
*/
DtoQuery<T> setParameter(String name, Object value);
/**
* The same as bind for positioned parameters.
*/
DtoQuery<T> setParameter(int position, Object value);
/**
* Set the index of the first row of the results to return.
*/
DtoQuery<T> setFirstRow(int firstRow);
/**
* Set the maximum number of query results to return.
*/
DtoQuery<T> setMaxRows(int maxRows);
/**
* When resultSet columns are not able to be mapped to a bean property then instead of
* throwing effectively skip reading that column.
*/
DtoQuery<T> setRelaxedMode();
/**
* Set a label on the query to make it easier to identify queries related to query execution statistics.
*
* @param label A label that is unique to the DTO bean type.
*/
DtoQuery<T> setLabel(String label);
/**
* Set a timeout on this query.
* <p>
* This will typically result in a call to setQueryTimeout() on a
* preparedStatement. If the timeout occurs an exception will be thrown - this
* will be a SQLException wrapped up in a PersistenceException.
* </p>
*
* @param secs the query timeout limit in seconds. Zero means there is no limit.
*/
DtoQuery<T> setTimeout(int secs);
/**
* A hint which for JDBC translates to the Statement.fetchSize().
* <p>
* Gives the JDBC driver a hint as to the number of rows that should be
* fetched from the database when more rows are needed for ResultSet.
* </p>
*/
DtoQuery<T> setBufferFetchSizeHint(int bufferFetchSizeHint);
}
+80 -2
View File
@@ -3,11 +3,13 @@ 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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
@@ -15,6 +17,7 @@ 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;
@@ -113,10 +116,11 @@ 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.
*/
@@ -371,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
@@ -641,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>
@@ -1053,6 +1116,21 @@ public final class Ebean {
return serverMgr.getDefaultServer().findNative(beanType, nativeSql);
}
/**
* Create a Query for DTO beans.
* <p>
* DTO beans are just normal bean like classes with public constructor(s) and setters.
* They do not need to be registered with Ebean before use.
* </p>
*
* @param dtoType The type of the DTO bean the rows will be mapped into.
* @param sql The SQL query to execute.
* @param <T> The type of the DTO bean.
*/
public static <T> DtoQuery<T> findDto(Class<T> dtoType, String sql) {
return serverMgr.getDefaultServer().findDto(dtoType, sql);
}
/**
* Create an Update query to perform a bulk update.
* <p>
+69 -1
View File
@@ -4,13 +4,13 @@ import io.ebean.annotation.TxIsolation;
import io.ebean.cache.ServerCacheManager;
import io.ebean.config.ServerConfig;
import io.ebean.meta.MetaInfoManager;
import io.ebean.plugin.Property;
import io.ebean.plugin.SpiServer;
import io.ebean.text.csv.CsvReader;
import io.ebean.text.json.JsonContext;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.NonUniqueResultException;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
@@ -424,6 +424,19 @@ public interface EbeanServer {
*/
<T> Update<T> createUpdate(Class<T> beanType, String ormUpdate);
/**
* Create a Query for DTO beans.
* <p>
* DTO beans are just normal bean like classes with public constructor(s) and setters.
* They do not need to be registered with Ebean before use.
* </p>
*
* @param dtoType The type of the DTO bean the rows will be mapped into.
* @param sql The SQL query to execute.
* @param <T> The type of the DTO bean.
*/
<T> DtoQuery<T> findDto(Class<T> dtoType, String sql);
/**
* Create a SqlQuery for executing native sql
* query statements.
@@ -1461,6 +1474,61 @@ public interface EbeanServer {
*/
int saveAll(Collection<?> beans, Transaction transaction) throws OptimisticLockException;
/**
* This method checks the uniqueness of a bean. I.e. if the save will work. It will return the
* properties that violates an unique / primary key. This may be done in an UI save action to
* validate if the user has entered correct values.
* <p>
* Note: This method queries the DB for uniqueness of all indices, so do not use it in a batch update.
* <p>
* Note: This checks only the root bean!
* <p>
* <pre>{@code
*
* // there is a unique constraint on title
*
* Document doc = new Document();
* doc.setTitle("One flew over the cuckoo's nest");
* doc.setBody("clashes with doc1");
*
* Set<Property> properties = server().checkUniqueness(doc);
*
* if (properties.isEmpty()) {
* // it is unique ... carry on
*
* } else {
* // build a user friendly message
* // to return message back to user
*
* String uniqueProperties = properties.toString();
*
* StringBuilder msg = new StringBuilder();
*
* properties.forEach((it)-> {
* Object propertyValue = it.getVal(doc);
* String propertyName = it.getName();
* msg.append(" property["+propertyName+"] value["+propertyValue+"]");
* });
*
* // uniqueProperties > [title]
* // custom msg > property[title] value[One flew over the cuckoo's nest]
*
* }
*
* }</pre>
*
* @param bean The entity bean to check uniqueness on
* @return a set of Properties if constraint validation was detected or empty list.
*/
@Nonnull
Set<Property> checkUniqueness(Object bean);
/**
* Same as {@link #checkUniqueness(Object)}. but with given transaction.
*/
@Nonnull
Set<Property> checkUniqueness(Object bean, Transaction transaction);
/**
* Marks the entity bean as dirty.
* <p>
@@ -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>.
*
+44
View File
@@ -236,6 +236,14 @@ public interface Query<T> {
*/
Query<T> asDraft();
/**
* Convert the query to a DTO bean query.
* <p>
* We effectively use the underlying ORM query to build the SQL and then execute
* and map it into DTO beans.
*/
<D> DtoQuery<D> asDto(Class<D> dtoClass);
/**
* Cancel the query execution if supported by the underlying database and
* driver.
@@ -828,6 +836,11 @@ public interface Query<T> {
*/
<A> A findSingleAttribute();
/**
* Return true if this is countDistinct query.
*/
boolean isCountDistinct();
/**
* Execute the query returning either a single bean or null (if no matching
* bean is found).
@@ -1272,6 +1285,37 @@ public interface Query<T> {
*/
Query<T> setDistinct(boolean isDistinct);
/**
* Extended version for setDistinct in conjunction with "findSingleAttributeList";
*
* <pre>{@code
*
* List<CountedValue<Order.Status>> orderStatusCount =
*
* Ebean.find(Order.class)
* .select("status")
* .where()
* .gt("orderDate", LocalDate.now().minusMonths(3))
*
* // fetch as single attribute with a COUNT
* .setCountDistinct(CountDistinctOrder.COUNT_DESC_ATTR_ASC)
* .findSingleAttributeList();
*
* for (CountedValue<Order.Status> entry : orderStatusCount) {
* System.out.println(" count:" + entry.getCount()+" orderStatus:" + entry.getValue() );
* }
*
* // produces
*
* count:3 orderStatus:NEW
* count:1 orderStatus:SHIPPED
* count:1 orderStatus:COMPLETE
*
* }</pre>
*
*/
Query<T> setCountDistinct(CountDistinctOrder orderBy);
/**
* Return the first row value.
*/
@@ -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
@@ -529,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.
*/
@@ -901,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();
}
}
}
@@ -1049,10 +1063,12 @@ 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();
}
/**
@@ -465,12 +465,14 @@ 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()) {
+128 -8
View File
@@ -293,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;
@@ -372,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<>();
@@ -470,6 +480,13 @@ 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.
@@ -1883,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.
*/
@@ -2597,7 +2642,8 @@ public class ServerConfig {
* Load settings from ebean.properties.
*/
public void loadFromProperties() {
loadFromProperties(PropertiesLoader.load());
this.properties = PropertiesLoader.load();
configureFromProperties();
}
/**
@@ -2605,7 +2651,14 @@ 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);
@@ -2679,6 +2732,7 @@ public class ServerConfig {
*/
protected void loadDataSourceSettings(PropertiesWrapper p) {
dataSourceConfig.loadSettings(p.properties, name);
readOnlyDataSourceConfig.loadSettings(p.properties, name);
}
/**
@@ -2740,6 +2794,7 @@ public class ServerConfig {
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);
@@ -2807,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);
@@ -2827,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);
}
@@ -2959,6 +3034,25 @@ 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.
*/
@@ -3008,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;
}
/**
@@ -3049,5 +3156,18 @@ public class ServerConfig {
public boolean useBinary() {
return binary;
}
/**
* Return true, if optimized packing should be used.
*/
public boolean useBinaryOptimized() {
return binaryOptimized;
}
}
public enum UuidVersion {
VERSION4,
VERSION1,
VERSION1RND
}
}
+22 -6
View File
@@ -8,32 +8,40 @@ public enum TenantMode {
/**
* No multi-tenancy.
*/
NONE(false),
NONE(false, true),
/**
* Each Tenant has their own Database (javax.sql.DataSource)
*/
DB(true),
DB(true, false),
/**
* Each Tenant has their own Database schema.
*/
SCHEMA(true),
SCHEMA(true, false),
/**
* Each Tenant has their own Database but with in connection pool
*/
CATALOG(true),
CATALOG(true, false),
/**
* Tenants share tables but have a discriminator/partition column that partitions the data.
*/
PARTITION(false);
PARTITION(false, true),
/**
* Each Tenant has their own Database (javax.sql.DataSource), and there is also one master-database
* (that holds configuration e.g.)
*/
DB_WITH_MASTER(true, true);
boolean dynamicDataSource;
boolean ddlEnabled;
TenantMode(boolean dynamicDataSource) {
TenantMode(boolean dynamicDataSource, boolean ddlEnabled) {
this.dynamicDataSource = dynamicDataSource;
this.ddlEnabled = ddlEnabled;
}
/**
@@ -42,4 +50,12 @@ public enum TenantMode {
public boolean isDynamicDataSource() {
return dynamicDataSource;
}
/**
* Returns true, if DDL is enabled.
*/
public boolean isDdlEnabled() {
return ddlEnabled;
}
}
@@ -28,16 +28,18 @@ class Loader {
private YamlLoader yamlLoader;
Loader() {
try {
Class<?> exists = Class.forName("org.yaml.snakeyaml.Yaml");
if (exists != null) {
yamlLoader = new YamlLoader(loadContext);
}
} catch (Exception e) {
// ignored
}
}
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.
@@ -115,7 +117,11 @@ class Loader {
void loadYaml(String resourcePath, Source source) {
if (yamlLoader != null) {
yamlLoader.load(resource(resourcePath, source));
try {
yamlLoader.load(resource(resourcePath, source));
} catch (Exception e) {
log.warn("Failed to read yml from:" + resourcePath, e);
}
}
}
@@ -1,5 +1,6 @@
package io.ebean.config.properties;
import java.util.Enumeration;
import java.util.Properties;
/**
@@ -66,4 +67,19 @@ public class PropertiesLoader {
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;
}
}
@@ -21,9 +21,12 @@ class YamlLoader {
this.loadContext = loadContext;
}
@SuppressWarnings("unchecked")
void load(InputStream is) {
if (is != null) {
loadMap(yaml.load(is), null);
for (Object map : yaml.loadAll(is)) {
loadMap((Map<String, Object>)map, null);
}
}
}
@@ -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;
}
@@ -104,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) {
@@ -112,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.
@@ -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);
}
@@ -1,6 +1,7 @@
package io.ebeaninternal.api;
import io.ebean.CacheMode;
import io.ebean.CountDistinctOrder;
import io.ebean.ExpressionList;
import io.ebean.OrderBy;
import io.ebean.PersistenceContextScope;
@@ -214,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.
*/
@@ -747,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.
*/
@@ -822,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 {
}
@@ -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");
}
@@ -268,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);
}
}
@@ -435,15 +435,15 @@ public class BaseTableDdl implements TableDdl {
protected void writeForeignKey(DdlWrite write, WriteForeignKey request) throws IOException {
String tableName = lowerTableName(request.table());
DdlBuffer fkeyBuffer = write.applyForeignKeys();
alterTableAddForeignKey(fkeyBuffer, request);
String tableName = lowerTableName(request.table());
if (request.indexName() != null) {
// no matching unique constraint so add the index
fkeyBuffer.append(platformDdl.createIndex(request.indexName(), tableName, request.cols())).endOfStatement();
}
alterTableAddForeignKey(fkeyBuffer, request);
fkeyBuffer.end();
write.dropAllForeignKeys()
@@ -455,7 +455,6 @@ public class BaseTableDdl implements TableDdl {
}
write.dropAllForeignKeys().end();
}
protected void alterTableAddForeignKey(DdlBuffer buffer, WriteForeignKey request) throws IOException {
@@ -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
@@ -13,6 +13,7 @@ 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;
@@ -238,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);
}
/**
@@ -326,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);
}
/**
@@ -335,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();
@@ -363,7 +364,7 @@ public class PlatformDdl {
StringBuilder buffer = new StringBuilder(90);
buffer
.append("alter table ").append(lowerTableName(request.table()))
.append(" add constraint ").append(request.fkName())
.append(" add constraint ").append(maxConstraintName(request.fkName()))
.append(" foreign key");
appendColumns(request.cols(), buffer);
buffer
@@ -410,14 +411,14 @@ public class PlatformDdl {
* 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);
}
/**
@@ -428,7 +429,7 @@ public class PlatformDdl {
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();
}
@@ -506,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;
}
@@ -622,4 +623,22 @@ public class PlatformDdl {
}
/**
* 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;
}
}
@@ -6,6 +6,9 @@ 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(",");
}
@@ -43,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;
}
@@ -57,7 +57,8 @@ 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.
@@ -102,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();
@@ -145,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();
}
@@ -37,6 +37,8 @@ public class DdlScript {
protected String name;
@XmlAttribute(name = "platforms")
protected String platforms;
@XmlAttribute(name = "drop")
protected boolean drop;
/**
* Gets the value of the value property.
@@ -98,4 +100,18 @@ public class DdlScript {
this.platforms = value;
}
/**
* Return if this a drop script.
*/
public boolean isDrop() {
return drop;
}
/**
* Sets that this is a drop script.
*/
public void setDrop(boolean drop) {
this.drop = drop;
}
}
@@ -1,10 +1,9 @@
package io.ebeaninternal.extraddl.model;
import io.ebean.util.StringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.ebean.util.StringHelper;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
@@ -20,7 +19,7 @@ public class ExtraDdlXmlReader {
/**
* Return the combined extra DDL that should be run given the platform name.
*/
public static String buildExtra(String platformName) {
public static String buildExtra(String platformName, boolean drops) {
ExtraDdl read = ExtraDdlXmlReader.read("/extra-ddl.xml");
if (read == null) {
@@ -28,7 +27,7 @@ public class ExtraDdlXmlReader {
}
StringBuilder sb = new StringBuilder(300);
for (DdlScript script : read.getDdlScript()) {
if (matchPlatform(platformName, script.getPlatforms())) {
if (script.isDrop() == drops && matchPlatform(platformName, script.getPlatforms())) {
logger.debug("include script {}", script.getName());
String value = script.getValue();
sb.append(value);
@@ -22,4 +22,13 @@ public interface MetricFactory {
*/
TimedMetric createTimedMetric(String name);
/**
* Create a Timed metric.
*/
QueryPlanMetric createQueryPlanMetric(Class<?> type, String label, String sql);
/**
* Return a instance used to collect Query plan metrics.
*/
QueryPlanCollector createCollector(boolean reset);
}
@@ -0,0 +1,26 @@
package io.ebeaninternal.metric;
import io.ebean.meta.MetaQueryMetric;
import java.util.List;
/**
* Object used to collect query plan metrics.
*/
public interface QueryPlanCollector {
/**
* Return true if the statistics should be reset.
*/
boolean isReset();
/**
* Add the query plan statistic.
*/
void add(MetaQueryMetric stats);
/**
* Return all the collected query plan statistics.
*/
List<MetaQueryMetric> complete();
}
@@ -0,0 +1,17 @@
package io.ebeaninternal.metric;
/**
* Internal Query plan metric holder.
*/
public interface QueryPlanMetric {
/**
* Return the underlying timed metric.
*/
TimedMetric getMetric();
/**
* Collect the non-empty query plan metrics.
*/
void collect(QueryPlanCollector collector);
}
@@ -12,7 +12,12 @@ public interface TimedMetric {
/**
* Add a time event (usually in microseconds).
*/
void add(long value);
void add(long micros);
/**
* Add a time event with the number of loaded beans or rows.
*/
void add(long micros, long beans);
/**
* Return true if there are no metrics collected since the last collection.
@@ -0,0 +1,190 @@
package io.ebeaninternal.server.core;
import io.ebean.EbeanServer;
import io.ebean.Transaction;
import io.ebean.util.JdbcClose;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiSqlBinding;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.lib.util.Str;
import io.ebeaninternal.server.persist.Binder;
import io.ebeaninternal.server.persist.TrimLogSql;
import io.ebeaninternal.server.transaction.TransactionManager;
import io.ebeaninternal.server.util.BindParamsParser;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Wraps the objects involved in executing a SQL / Relational Query.
*/
public abstract class AbstractSqlQueryRequest {
private final SpiSqlBinding query;
protected final SpiEbeanServer server;
protected SpiTransaction trans;
private boolean createdTransaction;
protected String sql;
protected ResultSet resultSet;
protected String bindLog = "";
protected PreparedStatement pstmt;
protected long startNano;
/**
* Create the BeanFindRequest.
*/
AbstractSqlQueryRequest(SpiEbeanServer server, SpiSqlBinding query, Transaction t) {
this.server = server;
this.query = query;
this.trans = (SpiTransaction) t;
}
/**
* Create a transaction if none currently exists.
*/
public void initTransIfRequired() {
if (trans == null) {
trans = server.currentServerTransaction();
if (trans == null || !trans.isActive()) {
// create a local readOnly transaction
trans = server.createQueryTransaction(null);
createdTransaction = true;
}
}
}
/**
* End the transaction if it was locally created.
*/
public void endTransIfRequired() {
if (createdTransaction) {
trans.commit();
}
}
public EbeanServer getServer() {
return server;
}
public SpiTransaction getTransaction() {
return trans;
}
public boolean isLogSql() {
return trans.isLogSql();
}
/**
* Set the resultSet and associated query plan if known.
*/
abstract void setResultSet(ResultSet resultSet, Object queryPlanKey) throws SQLException;
/**
* Return the bindLog for this request.
*/
public String getBindLog() {
return bindLog;
}
/**
* Return true if we can navigate to the next row.
*/
public boolean next() throws SQLException {
return resultSet.next();
}
protected abstract void requestComplete();
/**
* Close the underlying resources.
*/
public void close() {
requestComplete();
JdbcClose.close(resultSet);
JdbcClose.close(pstmt);
}
/**
* Prepare the SQL taking into account named bind parameters.
*/
private void prepareSql() {
String sql = query.getQuery();
BindParams bindParams = query.getBindParams();
if (!bindParams.isEmpty()) {
// convert any named parameters if required
sql = BindParamsParser.parse(bindParams, sql);
}
this.sql = limitOffset(sql);
}
private String limitOffset(String sql) {
int firstRow = query.getFirstRow();
int maxRows = query.getMaxRows();
if (firstRow > 0 || maxRows > 0) {
return server.getDatabasePlatform().getBasicSqlLimiter().limit(sql, firstRow, maxRows);
}
return sql;
}
/**
* Prepare and execute the SQL using the Binder.
*/
public void executeSql(Binder binder, SpiQuery.Type type) throws SQLException {
executeAsSql(binder);
}
protected void executeAsSql(Binder binder) throws SQLException {
startNano = System.nanoTime();
prepareSql();
Connection conn = trans.getInternalConnection();
pstmt = conn.prepareStatement(sql);
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
}
if (query.getBufferFetchSizeHint() > 0) {
pstmt.setFetchSize(query.getBufferFetchSizeHint());
}
BindParams bindParams = query.getBindParams();
if (!bindParams.isEmpty()) {
this.bindLog = binder.bind(bindParams, pstmt, conn);
}
if (isLogSql()) {
String logSql = TrimLogSql.trim(sql);
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
logSql = Str.add(logSql, "; --bind(", bindLog, ")");
}
trans.logSql(logSql);
}
setResultSet(pstmt.executeQuery(), null);
}
/**
* Return the SQL executed for this query.
*/
public String getSql() {
return sql;
}
}
@@ -176,7 +176,7 @@ public final class BasicTypeConverter implements Serializable {
/**
* Convert the value to a UUID.
*/
public static UUID toUUID(Object value) {
public static UUID toUUID(Object value, boolean optimizedBinary) {
if (value == null) {
return null;
@@ -188,7 +188,7 @@ public final class BasicTypeConverter implements Serializable {
return UUID.fromString((String) value);
}
if (value instanceof byte[]) {
return ScalarTypeUUIDBinary.convertFromBytes((byte[]) value);
return ScalarTypeUUIDBinary.convertFromBytes((byte[]) value, optimizedBinary);
}
return UUID.fromString(value.toString());
}
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.core;
import io.ebean.meta.MetaInfoManager;
import io.ebean.meta.MetaObjectGraphNodeStats;
import io.ebean.meta.MetaQueryMetric;
import io.ebean.meta.MetaQueryPlanStatistic;
import io.ebean.meta.MetaTimedMetric;
import io.ebeaninternal.server.deploy.BeanDescriptor;
@@ -26,6 +27,11 @@ public class DefaultMetaInfoManager implements MetaInfoManager {
return server.collectTransactionStatistics(reset);
}
@Override
public List<MetaQueryMetric> collectQueryStatistics(boolean reset) {
return server.collectQueryStatistics(reset);
}
@Override
public List<MetaQueryPlanStatistic> collectQueryPlanStatistics(boolean reset) {
@@ -5,7 +5,9 @@ import io.ebean.BackgroundExecutor;
import io.ebean.BeanState;
import io.ebean.CallableSql;
import io.ebean.DocumentStore;
import io.ebean.DtoQuery;
import io.ebean.ExpressionFactory;
import io.ebean.ExpressionList;
import io.ebean.Filter;
import io.ebean.FutureIds;
import io.ebean.FutureList;
@@ -46,9 +48,11 @@ import io.ebean.event.BeanPersistController;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.meta.MetaInfoManager;
import io.ebean.meta.MetaQueryMetric;
import io.ebean.meta.MetaTimedMetric;
import io.ebean.plugin.BeanType;
import io.ebean.plugin.Plugin;
import io.ebean.plugin.Property;
import io.ebean.plugin.SpiServer;
import io.ebean.text.csv.CsvReader;
import io.ebean.text.json.JsonContext;
@@ -56,6 +60,7 @@ import io.ebeaninternal.api.LoadBeanRequest;
import io.ebeaninternal.api.LoadManyRequest;
import io.ebeaninternal.api.ScopedTransaction;
import io.ebeaninternal.api.SpiBackgroundExecutor;
import io.ebeaninternal.api.SpiDtoQuery;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiJsonContext;
import io.ebeaninternal.api.SpiQuery;
@@ -72,6 +77,8 @@ import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.InheritInfo;
import io.ebeaninternal.server.dto.DtoBeanDescriptor;
import io.ebeaninternal.server.dto.DtoBeanManager;
import io.ebeaninternal.server.el.ElFilter;
import io.ebeaninternal.server.grammer.EqlParser;
import io.ebeaninternal.server.lib.ShutdownManager;
@@ -84,6 +91,8 @@ import io.ebeaninternal.server.query.LimitOffsetPagedList;
import io.ebeaninternal.server.query.QueryFutureIds;
import io.ebeaninternal.server.query.QueryFutureList;
import io.ebeaninternal.server.query.QueryFutureRowCount;
import io.ebeaninternal.server.query.dto.DtoQueryEngine;
import io.ebeaninternal.server.querydefn.DefaultDtoQuery;
import io.ebeaninternal.server.querydefn.DefaultOrmQuery;
import io.ebeaninternal.server.querydefn.DefaultOrmUpdate;
import io.ebeaninternal.server.querydefn.DefaultRelationalQuery;
@@ -104,7 +113,10 @@ import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -142,9 +154,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final OrmQueryEngine queryEngine;
private final RelationalQueryEngine relationalQueryEngine;
private final DtoQueryEngine dtoQueryEngine;
private final ServerCacheManager serverCacheManager;
private final DtoBeanManager dtoBeanManager;
private final BeanDescriptorManager beanDescriptorManager;
private final AutoTuneService autoTuneService;
@@ -215,6 +229,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
public DefaultServer(InternalConfiguration config, ServerCacheManager cache) {
this.dtoBeanManager = config.getDtoBeanManager();
this.serverConfig = config.getServerConfig();
this.objectGraphStats = new ConcurrentHashMap<>();
this.metaInfoManager = new DefaultMetaInfoManager(this);
@@ -244,6 +259,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
this.persister = config.createPersister(this);
this.queryEngine = config.createOrmQueryEngine();
this.relationalQueryEngine = config.createRelationalQueryEngine();
this.dtoQueryEngine = config.createDtoQueryEngine();
this.autoTuneService = config.createAutoTuneService(this);
this.readAuditPrepare = config.getReadAuditPrepare();
@@ -962,6 +978,28 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return new DefaultOrmUpdate<>(beanType, this, desc.getBaseTable(), ormUpdate);
}
@Override
public <T> DtoQuery<T> findDto(Class<T> dtoType, String sql) {
DtoBeanDescriptor<T> descriptor = dtoBeanManager.getDescriptor(dtoType);
return new DefaultDtoQuery<>(this, descriptor, sql.trim());
}
@Override
public <T> DtoQuery<T> findDto(Class<T> dtoType, SpiQuery<?> ormQuery) {
DtoBeanDescriptor<T> descriptor = dtoBeanManager.getDescriptor(dtoType);
return new DefaultDtoQuery<>(this, descriptor, ormQuery);
}
@Override
public SpiResultSet findResultSet(SpiQuery<?> ormQuery, SpiTransaction transaction) {
SpiOrmQueryRequest<?> request = createQueryRequest(ormQuery.getType(), ormQuery, transaction);
request.initTransIfRequired();
return request.findResultSet();
}
@Override
public SqlQuery createSqlQuery(String sql) {
return new DefaultRelationalQuery(this, sql.trim());
@@ -1495,6 +1533,54 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Override
public <T> void findDtoEach(SpiDtoQuery<T> query, Consumer<T> consumer) {
DtoQueryRequest<T> request = new DtoQueryRequest<>(this, dtoQueryEngine, query);
try {
request.initTransIfRequired();
request.findEach(consumer);
} finally {
request.endTransIfRequired();
}
}
@Override
public <T> void findDtoEachWhile(SpiDtoQuery<T> query, Predicate<T> consumer) {
DtoQueryRequest<T> request = new DtoQueryRequest<>(this, dtoQueryEngine, query);
try {
request.initTransIfRequired();
request.findEachWhile(consumer);
} finally {
request.endTransIfRequired();
}
}
@Override
public <T> List<T> findDtoList(SpiDtoQuery<T> query) {
DtoQueryRequest<T> request = new DtoQueryRequest<>(this, dtoQueryEngine, query);
try {
request.initTransIfRequired();
return request.findList();
} finally {
request.endTransIfRequired();
}
}
@Override
public <T> T findDtoOne(SpiDtoQuery<T> query) {
DtoQueryRequest<T> request = new DtoQueryRequest<>(this, dtoQueryEngine, query);
try {
request.initTransIfRequired();
return extractUnique(request.findList());
} finally {
request.endTransIfRequired();
}
}
/**
* Persist the bean by either performing an insert or update.
*/
@@ -2109,4 +2195,81 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
public List<MetaTimedMetric> collectTransactionStatistics(boolean reset) {
return transactionManager.collectTransactionStatistics(reset);
}
@Override
public Set<Property> checkUniqueness(Object bean) {
return checkUniqueness(bean, null);
}
@Override
public Set<Property> checkUniqueness(Object bean, Transaction transaction) {
EntityBean entityBean = checkEntityBean(bean);
BeanDescriptor<?> beanDesc = getBeanDescriptor(entityBean.getClass());
BeanProperty idProperty = beanDesc.getIdProperty();
// if the ID of the Property is null we are unable to check uniqueness
if (idProperty == null) {
return Collections.emptySet();
}
Object id = idProperty.getVal(entityBean);
if (entityBean._ebean_intercept().isNew() && id != null) {
// Primary Key is changeable only on new models - so skip check if we are not
// new.
Query<?> query = new DefaultOrmQuery<>(beanDesc, this, expressionFactory);
query.setId(id);
if (findCount(query, transaction) > 0) {
Set<Property> ret = new HashSet<>();
ret.add(idProperty);
return ret;
}
}
for (BeanProperty[] props : beanDesc.getUniqueProps()) {
Set<Property> ret = checkUniqueness(entityBean, beanDesc, props, transaction);
if (ret != null) {
return ret;
}
}
return Collections.emptySet();
}
/**
* Returns a set of properties if saving the bean will violate the unique constraints
* (definded by given properties).
*/
private Set<Property> checkUniqueness(EntityBean entityBean, BeanDescriptor<?> beanDesc, BeanProperty[] props,
Transaction transaction) {
BeanProperty idProperty = beanDesc.getIdProperty();
Query<?> query = new DefaultOrmQuery<>(beanDesc, this, expressionFactory);
ExpressionList<?> exprList = query.where();
if (!entityBean._ebean_intercept().isNew()) {
// if model is not new, exclude ourself.
exprList.ne(idProperty.getName(), idProperty.getVal(entityBean));
}
for (Property prop : props) {
Object value = prop.getVal(entityBean);
if (value == null) {
return null;
}
exprList.eq(prop.getName(), value);
}
if (findCount(query, transaction) > 0) {
Set<Property> ret = new LinkedHashSet<>();
for (Property prop : props) {
ret.add(prop);
}
return ret;
}
return null;
}
public List<MetaQueryMetric> collectQueryStatistics(boolean reset) {
return dtoBeanManager.collectStats(reset);
}
}
@@ -0,0 +1,129 @@
package io.ebeaninternal.server.core;
import io.ebeaninternal.api.SpiDtoQuery;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.dto.DtoColumn;
import io.ebeaninternal.server.dto.DtoMappingRequest;
import io.ebeaninternal.server.dto.DtoQueryPlan;
import io.ebeaninternal.server.persist.Binder;
import io.ebeaninternal.server.query.dto.DtoQueryEngine;
import io.ebeaninternal.server.type.DataReader;
import io.ebeaninternal.server.type.RsetDataReader;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* Wraps the objects involved in executing a DtoQuery.
*/
public final class DtoQueryRequest<T> extends AbstractSqlQueryRequest {
private final SpiDtoQuery<T> query;
private final DtoQueryEngine queryEngine;
private DtoQueryPlan plan;
private DataReader dataReader;
private int beanCount;
DtoQueryRequest(SpiEbeanServer server, DtoQueryEngine engine, SpiDtoQuery<T> query) {
super(server, query, null);
this.queryEngine = engine;
this.query = query;
}
/**
* Prepare and execute the SQL using the Binder.
*/
@Override
public void executeSql(Binder binder, SpiQuery.Type type) throws SQLException {
SpiQuery<?> ormQuery = query.getOrmQuery();
if (ormQuery != null) {
ormQuery.setType(type);
ormQuery.setManualId(true);
// execute the underlying ORM query returning the ResultSet
SpiResultSet result = server.findResultSet(ormQuery, trans);
this.pstmt = result.getStatement();
this.sql = ormQuery.getGeneratedSql();
setResultSet(result.getResultSet(), ormQuery.getQueryPlanKey());
} else {
// native SQL query execution
executeAsSql(binder);
}
}
@Override
protected void setResultSet(ResultSet resultSet, Object queryPlanKey) throws SQLException {
this.resultSet = resultSet;
this.dataReader = new RsetDataReader(server.getDataTimeZone(), resultSet);
obtainPlan(queryPlanKey);
}
private void obtainPlan(Object planKey) throws SQLException {
if (planKey == null) {
planKey = query.planKey();
}
plan = query.getQueryPlan(planKey);
if (plan == null) {
plan = query.buildPlan(mappingRequest());
query.putQueryPlan(planKey, plan);
}
}
@Override
protected void requestComplete() {
if (plan != null) {
long exeMicros = (System.nanoTime() - startNano) / 1000L;
plan.collect(exeMicros, beanCount);
}
}
public void findEach(Consumer<T> consumer) {
queryEngine.findEach(this, consumer);
}
public void findEachWhile(Predicate<T> consumer) {
queryEngine.findEachWhile(this, consumer);
}
public List<T> findList() {
return queryEngine.findList(this);
}
@SuppressWarnings("unchecked")
public T readNextBean() throws SQLException {
beanCount++;
dataReader.resetColumnPosition();
return (T)plan.readRow(dataReader);
}
private DtoMappingRequest mappingRequest() throws SQLException {
return new DtoMappingRequest(query, sql, readMeta());
}
private DtoColumn[] readMeta() throws SQLException {
ResultSetMetaData metaData = resultSet.getMetaData();
int cols = metaData.getColumnCount();
DtoColumn[] meta = new DtoColumn[cols];
for (int i = 0; i < cols; i++) {
int pos = i+1;
String columnLabel = metaData.getColumnLabel(pos);
if (columnLabel == null) {
columnLabel = metaData.getColumnName(pos);
}
meta[i] = new DtoColumn(columnLabel);
}
return meta;
}
}
@@ -40,6 +40,7 @@ import io.ebeaninternal.server.deploy.generatedproperty.GeneratedPropertyFactory
import io.ebeaninternal.server.deploy.parse.DeployCreateProperties;
import io.ebeaninternal.server.deploy.parse.DeployInherit;
import io.ebeaninternal.server.deploy.parse.DeployUtil;
import io.ebeaninternal.server.dto.DtoBeanManager;
import io.ebeaninternal.server.expression.DefaultExpressionFactory;
import io.ebeaninternal.server.persist.Binder;
import io.ebeaninternal.server.persist.DefaultPersister;
@@ -48,6 +49,7 @@ import io.ebeaninternal.server.persist.platform.PostgresMultiValueBind;
import io.ebeaninternal.server.query.CQueryEngine;
import io.ebeaninternal.server.query.DefaultOrmQueryEngine;
import io.ebeaninternal.server.query.DefaultRelationalQueryEngine;
import io.ebeaninternal.server.query.dto.DtoQueryEngine;
import io.ebeaninternal.server.readaudit.DefaultReadAuditLogger;
import io.ebeaninternal.server.readaudit.DefaultReadAuditPrepare;
import io.ebeaninternal.server.text.json.DJsonContext;
@@ -97,6 +99,8 @@ public class InternalConfiguration {
private final TypeManager typeManager;
private final DtoBeanManager dtoBeanManager;
private final DataTimeZone dataTimeZone;
private final Binder binder;
@@ -149,6 +153,7 @@ public class InternalConfiguration {
this.deployCreateProperties = new DeployCreateProperties(typeManager);
this.deployUtil = new DeployUtil(typeManager, serverConfig);
this.dtoBeanManager = new DtoBeanManager(typeManager);
this.beanDescriptorManager = new BeanDescriptorManager(this);
Map<String, String> asOfTableMapping = beanDescriptorManager.deploy();
@@ -294,8 +299,12 @@ public class InternalConfiguration {
return AutoTuneServiceFactory.create(server, serverConfig);
}
public DtoQueryEngine createDtoQueryEngine() {
return new DtoQueryEngine(binder);
}
public RelationalQueryEngine createRelationalQueryEngine() {
return new DefaultRelationalQueryEngine(binder, serverConfig.getDatabaseBooleanTrue());
return new DefaultRelationalQueryEngine(binder, serverConfig.getDatabaseBooleanTrue(), serverConfig.getDbTypeConfig().getDbUuid().useBinaryOptimized());
}
public OrmQueryEngine createOrmQueryEngine() {
@@ -409,6 +418,7 @@ public class InternalConfiguration {
private DataSourceSupplier dataSource() {
switch (serverConfig.getTenantMode()) {
case DB:
case DB_WITH_MASTER:
return new MultiTenantDbSupplier(serverConfig.getCurrentTenantProvider(), serverConfig.getTenantDataSourceProvider());
case SCHEMA:
return new MultiTenantDbSchemaSupplier(serverConfig.getCurrentTenantProvider(), serverConfig.getDataSource(), serverConfig.getReadOnlyDataSource(), serverConfig.getTenantSchemaProvider());
@@ -434,7 +444,7 @@ public class InternalConfiguration {
/**
* Create the TransactionScopeManager taking into account JTA or external transaction manager.
*/
public TransactionScopeManager createTransactionScopeManager() {
private TransactionScopeManager createTransactionScopeManager() {
ExternalTransactionManager externalTransactionManager = serverConfig.getExternalTransactionManager();
if (externalTransactionManager == null && serverConfig.isUseJtaTransactionManager()) {
@@ -504,4 +514,8 @@ public class InternalConfiguration {
public MultiValueBind getMultiValueBind() {
return multiValueBind;
}
public DtoBeanManager getDtoBeanManager() {
return dtoBeanManager;
}
}
@@ -13,6 +13,11 @@ import java.util.List;
*/
public interface OrmQueryEngine {
/**
* Execute the ORM query returning the JDBC ResultSet (for DTO query processing).
*/
<T> SpiResultSet findResultSet(OrmQueryRequest<T> request);
/**
* Execute the 'find by id' query returning a single bean.
*/
@@ -222,7 +222,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
if (query.isRawSql()) {
return new DeployPropertyParserMap(query.getRawSql().getColumnMapping().getMapping());
} else {
return beanDescriptor.createDeployPropertyParser();
return beanDescriptor.parser();
}
}
@@ -353,6 +353,13 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
return query.getType() == Type.BEAN;
}
/**
* Return true if this is a findEach, findIterate type query where we expect many results.
*/
public boolean isFindIterate() {
return query.getType() == Type.ITERATE;
}
/**
* Execute the query as a delete.
*/
@@ -376,6 +383,11 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
return rows;
}
@Override
public SpiResultSet findResultSet() {
return queryEngine.findResultSet(this);
}
/**
* Execute the query as findById.
*/
@@ -9,6 +9,11 @@ import java.util.function.Predicate;
public interface RelationalQueryEngine {
/**
* Return a new SqlRow with appropriate mapping for DB true and optimised binary UUID etc.
*/
SqlRow createSqlRow(int estimateCapacity);
/**
* Find a list of beans using relational query.
*/
@@ -1,23 +1,11 @@
package io.ebeaninternal.server.core;
import io.ebean.EbeanServer;
import io.ebean.SqlQuery;
import io.ebean.SqlRow;
import io.ebean.Transaction;
import io.ebean.util.JdbcClose;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiSqlQuery;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.lib.util.Str;
import io.ebeaninternal.server.persist.Binder;
import io.ebeaninternal.server.persist.TrimLogSql;
import io.ebeaninternal.server.query.DefaultSqlRow;
import io.ebeaninternal.server.transaction.TransactionManager;
import io.ebeaninternal.server.util.BindParamsParser;
import io.ebeaninternal.api.SpiSqlBinding;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
@@ -29,63 +17,36 @@ import java.util.function.Predicate;
/**
* Wraps the objects involved in executing a SqlQuery.
*/
public final class RelationalQueryRequest {
private final SpiSqlQuery query;
public final class RelationalQueryRequest extends AbstractSqlQueryRequest {
private final RelationalQueryEngine queryEngine;
private final SpiEbeanServer ebeanServer;
private SpiTransaction trans;
private boolean createdTransaction;
private String sql;
private ResultSet resultSet;
private int rowCount;
private String bindLog = "";
private String[] propertyNames;
private int estimateCapacity;
private PreparedStatement pstmt;
private int rows;
/**
* Create the BeanFindRequest.
*/
RelationalQueryRequest(SpiEbeanServer server, RelationalQueryEngine engine, SqlQuery q, Transaction t) {
this.ebeanServer = server;
super(server, (SpiSqlBinding) q, t);
this.queryEngine = engine;
this.query = (SpiSqlQuery) q;
this.trans = (SpiTransaction) t;
}
/**
* Create a transaction if none currently exists.
*/
public void initTransIfRequired() {
if (trans == null) {
trans = ebeanServer.currentServerTransaction();
if (trans == null || !trans.isActive()) {
// create a local readOnly transaction
trans = ebeanServer.beginServerTransaction();
createdTransaction = true;
}
}
@Override
protected void setResultSet(ResultSet resultSet, Object planKey) throws SQLException {
this.resultSet = resultSet;
this.propertyNames = getPropertyNames();
// calculate the initialCapacity of the Map to reduce rehashing
float initCap = (propertyNames.length) / 0.7f;
this.estimateCapacity = (int) initCap + 1;
}
/**
* End the transaction if it was locally created.
*/
public void endTransIfRequired() {
if (createdTransaction) {
ebeanServer.commitTransaction();
}
@Override
protected void requestComplete() {
}
public void findEach(Consumer<SqlRow> consumer) {
@@ -100,37 +61,6 @@ public final class RelationalQueryRequest {
return queryEngine.findList(this);
}
/**
* Return the find that is to be performed.
*/
public SpiSqlQuery getQuery() {
return query;
}
public EbeanServer getEbeanServer() {
return ebeanServer;
}
public SpiTransaction getTransaction() {
return trans;
}
public boolean isLogSql() {
return trans.isLogSql();
}
public boolean isLogSummary() {
return trans.isLogSummary();
}
private void setResultSet(ResultSet resultSet) throws SQLException {
this.resultSet = resultSet;
this.propertyNames = getPropertyNames();
// calculate the initialCapacity of the Map to reduce rehashing
float initCap = (propertyNames.length) / 0.7f;
this.estimateCapacity = (int) initCap + 1;
}
/**
* Build the list of property names.
*/
@@ -146,36 +76,14 @@ public final class RelationalQueryRequest {
return propNames.toArray(new String[propNames.size()]);
}
/**
* Return the bindLog for this request.
*/
public String getBindLog() {
return bindLog;
}
/**
* Return true if we can navigate to the next row.
*/
public boolean next() throws SQLException {
rowCount++;
return resultSet.next();
}
/**
* Close the underlying resources.
*/
public void close() {
JdbcClose.close(resultSet);
JdbcClose.close(pstmt);
}
/**
* Read and return the next SqlRow.
*/
public SqlRow createNewRow(String dbTrueValue) throws SQLException {
public SqlRow createNewRow() throws SQLException {
SqlRow sqlRow = new DefaultSqlRow(estimateCapacity, 0.75f, dbTrueValue);
rows++;
SqlRow sqlRow = queryEngine.createSqlRow(estimateCapacity);
int index = 0;
for (String propertyName : propertyNames) {
index++;
@@ -185,76 +93,11 @@ public final class RelationalQueryRequest {
return sqlRow;
}
/**
* Prepare the SQL taking into account named bind parameters.
*/
private void prepareSql() {
String sql = query.getQuery();
BindParams bindParams = query.getBindParams();
if (!bindParams.isEmpty()) {
// convert any named parameters if required
sql = BindParamsParser.parse(bindParams, sql);
public void logSummary() {
if (trans.isLogSummary()) {
long micros = (System.nanoTime() - startNano) / 1000L;
trans.logSummary("SqlQuery rows[" + rows + "] micros[" + micros + "] bind[" + bindLog + "]");
}
this.sql = limitOffset(sql);
}
private String limitOffset(String sql) {
int firstRow = query.getFirstRow();
int maxRows = query.getMaxRows();
if (firstRow > 0 || maxRows > 0) {
return ebeanServer.getDatabasePlatform().getBasicSqlLimiter().limit(sql, firstRow, maxRows);
}
return sql;
}
/**
* Prepare and execute the SQL using the Binder.
*/
public void executeSql(Binder binder) throws SQLException {
prepareSql();
Connection conn = trans.getInternalConnection();
// synchronise for query.cancel() support
pstmt = conn.prepareStatement(sql);
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
}
if (query.getBufferFetchSizeHint() > 0) {
pstmt.setFetchSize(query.getBufferFetchSizeHint());
}
BindParams bindParams = query.getBindParams();
if (!bindParams.isEmpty()) {
this.bindLog = binder.bind(bindParams, pstmt, conn);
}
if (isLogSql()) {
String logSql = TrimLogSql.trim(sql);
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
logSql = Str.add(logSql, "; --bind(", bindLog, ")");
}
trans.logSql(logSql);
}
setResultSet(pstmt.executeQuery());
}
/**
* Return the SQL executed for this query.
*/
public String getSql() {
return sql;
}
/**
* Return the rows read.
*/
public int getRowCount() {
return rowCount - 1;
}
}
@@ -114,6 +114,11 @@ public interface SpiOrmQueryRequest<T> extends BeanQueryRequest<T>, DocQueryRequ
*/
<A> List<A> findSingleAttributeList();
/**
* Execute returning the ResultSet.
*/
SpiResultSet findResultSet();
/**
* Try to get the query result from the query cache.
*/
@@ -0,0 +1,29 @@
package io.ebeaninternal.server.core;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
/**
* Hold the JDBC PreparedStatement and ResultSet.
*
* These both must be closed properly when done.
*/
public class SpiResultSet {
private final PreparedStatement statement;
private final ResultSet resultSet;
public SpiResultSet(PreparedStatement statement, ResultSet resultSet) {
this.statement = statement;
this.resultSet = resultSet;
}
public PreparedStatement getStatement() {
return statement;
}
public ResultSet getResultSet() {
return resultSet;
}
}
@@ -62,11 +62,13 @@ import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.persist.DmlUtil;
import io.ebeaninternal.server.query.CQueryPlan;
import io.ebeaninternal.server.query.CQueryPlanStatsCollector;
import io.ebeaninternal.server.query.SqlTreeProperty;
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import io.ebeaninternal.server.rawsql.SpiRawSql;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.type.ScalarType;
import io.ebeaninternal.util.SortByClause;
import io.ebeaninternal.util.SortByClauseParser;
import io.ebeanservice.docstore.api.DocStoreBeanAdapter;
@@ -112,6 +114,8 @@ public class BeanDescriptor<T> implements BeanType<T> {
private final ConcurrentHashMap<String, ElComparator<T>> comparatorCache = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, SqlTreeProperty> dynamicProperty = new ConcurrentHashMap<>();
private final Map<String, SpiRawSql> namedRawSql;
private final Map<String, String> namedQuery;
@@ -350,6 +354,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
protected final BeanProperty[] propertiesIndex;
private final BeanProperty[] propertiesGenInsert;
private final BeanProperty[] propertiesGenUpdate;
private final List<BeanProperty[]> propertiesUnique = new ArrayList<>();
/**
* The bean class name or the table name for MapBeans.
@@ -769,9 +774,43 @@ public class BeanDescriptor<T> implements BeanType<T> {
* Perform last initialisation for the descriptor.
*/
public void initLast() {
for (BeanProperty prop : propertiesNonTransient) {
if (prop.isUnique()) {
propertiesUnique.add(new BeanProperty[] { prop });
}
}
// convert unique columns to properties
if (indexDefinitions != null) {
for (IndexDefinition indexDef : indexDefinitions) {
if (indexDef.isUnique()) {
addUniqueColumns(indexDef);
}
}
}
docStoreEmbeddedInvalidation = docStoreAdapter.hasEmbeddedInvalidation();
}
private void addUniqueColumns(IndexDefinition indexDef) {
String[] cols = indexDef.getColumns();
BeanProperty[] props = new BeanProperty[cols.length];
for (int i = 0; i < cols.length; i++) {
String propName = findBeanPath("", cols[i]);
if (propName == null) {
return;
}
props[i] = findBeanProperty(propName);
}
if (props.length == 1) {
for (BeanProperty[] inserted : propertiesUnique) {
if (inserted.length == 1 && inserted[0].equals(props[0])) {
return; // do not insert duplicates
}
}
}
propertiesUnique.add(props);
}
/**
* Initialise the document mapping.
*/
@@ -1020,6 +1059,17 @@ public class BeanDescriptor<T> implements BeanType<T> {
return owner.getEncryptKey(tableName, columnName);
}
/**
* Return the Scalar type for the given JDBC type.
*/
public ScalarType<?> getScalarType(int jdbcType) {
return owner.getScalarType(jdbcType);
}
public ScalarType<?> getScalarType(String cast) {
return owner.getScalarType(cast);
}
/**
* Return true if this bean type has a default select clause that is not
* simply select all properties.
@@ -1536,7 +1586,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
}
}
public DeployPropertyParser createDeployPropertyParser() {
public DeployPropertyParser parser() {
return new DeployPropertyParser(this);
}
@@ -2402,6 +2452,26 @@ public class BeanDescriptor<T> implements BeanType<T> {
return null;
}
/**
* Return a 'dynamic property' used to read a formula.
*/
private SqlTreeProperty findSqlTreeFormula(String formulaExpression) {
return dynamicProperty.computeIfAbsent(formulaExpression, (formula) -> new FormulaPropertyPath(this, formula).build());
}
/**
* Return a property that is part of the SQL tree.
*
* The property can be a dynamic formula or a well known bean property.
*/
public SqlTreeProperty findSqlTreeProperty(String propName) {
if (propName.indexOf('(') > -1) {
return findSqlTreeFormula(propName);
}
return _findBeanProperty(propName);
}
/**
* Find a BeanProperty including searching the inheritance hierarchy.
* <p>
@@ -2420,7 +2490,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
return _findBeanProperty(propName);
}
private BeanProperty _findBeanProperty(String propName) {
BeanProperty _findBeanProperty(String propName) {
BeanProperty prop = propMap.get(propName);
if (prop == null && inheritInfo != null) {
// search in sub types...
@@ -3220,4 +3290,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
return jsonHelp.jsonReadObject(jsonRead, path);
}
public List<BeanProperty[]> getUniqueProps() {
return propertiesUnique;
}
}
@@ -52,7 +52,9 @@ import io.ebeaninternal.server.properties.BeanPropertiesReader;
import io.ebeaninternal.server.properties.BeanPropertyAccess;
import io.ebeaninternal.server.properties.EnhanceBeanPropertyAccess;
import io.ebeaninternal.server.query.CQueryPlan;
import io.ebeaninternal.server.type.ScalarType;
import io.ebeaninternal.server.type.ScalarTypeInteger;
import io.ebeaninternal.server.type.TypeManager;
import io.ebeaninternal.xmlmapping.XmlMappingReader;
import io.ebeaninternal.xmlmapping.model.XmAliasMapping;
import io.ebeaninternal.xmlmapping.model.XmColumnMapping;
@@ -139,6 +141,8 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
private final MultiValueBind multiValueBind;
private final TypeManager typeManager;
private int entityBeanCount;
private final boolean updateChangesOnly;
@@ -222,6 +226,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
this.dbIdentity = config.getDatabasePlatform().getDbIdentity();
this.deplyInherit = config.getDeployInherit();
this.deployUtil = config.getDeployUtil();
this.typeManager = deployUtil.getTypeManager();
this.beanManagerFactory = new BeanManagerFactory(config.getDatabasePlatform());
@@ -260,6 +265,16 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
}
}
@Override
public ScalarType<?> getScalarType(String cast) {
return typeManager.getScalarType(cast);
}
@Override
public ScalarType<?> getScalarType(int jdbcType) {
return typeManager.getScalarType(jdbcType);
}
/**
* Return the AsOfViewSuffix based on the DbHistorySupport.
*/
@@ -1463,7 +1478,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
prop.setGetter(beanPropertyAccess.getGetter(propertyIndex));
prop.setSetter(beanPropertyAccess.getSetter(propertyIndex));
if (prop.isAggregation()) {
prop.setAggregationPrefix(DetermineAggPath.manyPath(prop.getAggregation(), desc));
prop.setAggregationPrefix(DetermineAggPath.manyPath(prop.getRawAggregation(), desc));
}
}
}
@@ -6,6 +6,7 @@ import io.ebean.config.ServerConfig;
import io.ebeaninternal.server.cache.SpiCacheManager;
import io.ebeaninternal.server.deploy.id.IdBinder;
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import io.ebeaninternal.server.type.ScalarType;
import io.ebeanservice.docstore.api.DocStoreBeanAdapter;
/**
@@ -60,4 +61,14 @@ public interface BeanDescriptorMap {
* Create a doc store specific adapter for this bean type.
*/
<T> DocStoreBeanAdapter<T> createDocStoreBeanAdapter(BeanDescriptor<T> descriptor, DeployBeanDescriptor<T> deploy);
/**
* Return the scalarType for the given JDBC type.
*/
ScalarType<?> getScalarType(int jdbcType);
/**
* Return the scalarType for the given logical type.
*/
ScalarType<?> getScalarType(String cast);
}
@@ -24,6 +24,7 @@ import io.ebeaninternal.server.properties.BeanPropertyGetter;
import io.ebeaninternal.server.properties.BeanPropertySetter;
import io.ebeaninternal.server.query.SqlBeanLoad;
import io.ebeaninternal.server.query.SqlJoinType;
import io.ebeaninternal.server.query.SqlTreeProperty;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.server.type.DataBind;
@@ -55,7 +56,7 @@ import java.util.Set;
* Description of a property of a bean. Includes its deployment information such
* as database column mapping information.
*/
public class BeanProperty implements ElPropertyValue, Property {
public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty {
private static final Logger logger = LoggerFactory.getLogger(BeanProperty.class);
@@ -321,7 +322,7 @@ public class BeanProperty implements ElPropertyValue, Property {
this.dbColumn = tableAliasIntern(descriptor, deploy.getDbColumn(), false, null);
this.dbComment = deploy.getDbComment();
this.aggregation = deploy.getAggregation();
this.aggregation = deploy.parseAggregation();
this.sqlFormulaJoin = InternString.intern(deploy.getSqlFormulaJoin());
this.sqlFormulaSelect = InternString.intern(deploy.getSqlFormulaSelect());
this.formula = sqlFormulaSelect != null;
@@ -575,8 +576,7 @@ public class BeanProperty implements ElPropertyValue, Property {
public void appendSelect(DbSqlContext ctx, boolean subQuery) {
if (aggregation != null) {
ctx.appendRawColumn(aggregation);
ctx.appendFormulaSelect(aggregation);
} else if (formula) {
ctx.appendFormulaSelect(sqlFormulaSelect);
@@ -64,6 +64,11 @@ public interface DbSqlContext {
*/
void appendColumn(String column);
/**
* Parse and add formula with standard table alias replacement.
*/
void appendParseSelect(String parseSelect, String alias);
/**
* Append a Sql Formula select. This converts the "${ta}" keyword to the
* current table alias.
@@ -27,6 +27,8 @@ public abstract class DeployParser {
*/
protected static final char PERIOD = '.';
protected static final char OPEN_BRACKET = '(';
protected boolean encrypted;
protected String source;
@@ -43,6 +45,8 @@ public abstract class DeployParser {
protected char wordTerminator;
private StringBuilder wordBuffer;
protected abstract String convertWord();
public abstract String getDeployWord(String expression);
@@ -77,7 +81,9 @@ public abstract class DeployParser {
priorWord = deployWord;
}
if (pos < sourceLength) {
sb.append(wordTerminator);
if (wordTerminator != OPEN_BRACKET) {
sb.append(wordTerminator);
}
if (wordTerminator == SINGLE_QUOTE) {
readLiteral();
}
@@ -97,7 +103,7 @@ public abstract class DeployParser {
return false;
}
StringBuilder wordBuffer = new StringBuilder();
wordBuffer = new StringBuilder();
wordBuffer.append(source.charAt(pos));
while (++pos < sourceLength) {
char ch = source.charAt(pos);
@@ -172,6 +178,11 @@ public abstract class DeployParser {
* return true if the char is a letter, digit or underscore.
*/
private boolean isWordPart(char ch) {
if (ch == OPEN_BRACKET) {
// include in the 'word' such that "count(" formula doesn't clash with property "count"
wordBuffer.append(ch);
return false;
}
return Character.isLetterOrDigit(ch) || ch == UNDERSCORE || ch == PERIOD;
}
@@ -22,10 +22,29 @@ public final class DeployPropertyParser extends DeployParser {
private final Set<String> includes = new HashSet<>();
private boolean catchFirst;
private ElPropertyDeploy firstProp;
DeployPropertyParser(BeanDescriptor<?> beanDescriptor) {
this.beanDescriptor = beanDescriptor;
}
/**
* Set to true to catch the first property.
*/
public DeployPropertyParser setCatchFirst(boolean catchFirst) {
this.catchFirst = catchFirst;
return this;
}
/**
* Return the first property found by the parser.
*/
public ElPropertyDeploy getFirstProp() {
return firstProp;
}
@Override
public Set<String> getIncludes() {
return includes;
@@ -45,6 +64,9 @@ public final class DeployPropertyParser extends DeployParser {
if (elProp == null) {
return null;
} else {
if (catchFirst && firstProp == null) {
firstProp = elProp;
}
addIncludes(elProp.getElPrefix());
return elProp.getElPlaceholder(encrypted);
}
@@ -7,7 +7,7 @@ import java.util.Set;
/**
* Converts logical property names to database columns using a Map.
*/
public final class DeployPropertyParserMap extends DeployParser {
public class DeployPropertyParserMap extends DeployParser {
private final Map<String, String> map;
@@ -12,6 +12,10 @@ class DetermineAggPath {
*/
static String manyPath(String aggregation, DeployBeanDescriptor<?> desc) {
DetermineAggPath.Path path = paths(aggregation);
if (path.length() == 1) {
// a top level aggregation (so here we need to exclude Id property)
return null;
}
return path.getManyPath(0, desc);
}
@@ -0,0 +1,58 @@
package io.ebeaninternal.server.deploy;
import io.ebeaninternal.server.query.SqlBeanLoad;
import io.ebeaninternal.server.type.ScalarType;
import javax.persistence.PersistenceException;
/**
* Dynamic property based on aggregation (max, min, avg, count).
*/
class DynamicPropertyAggregationFormula extends DynamicPropertyBase {
private final String parsedFormula;
private final boolean aggregate;
private final BeanProperty asTarget;
private final String alias;
DynamicPropertyAggregationFormula(String name, ScalarType<?> scalarType, String parsedFormula, boolean aggregate, BeanProperty asTarget, String alias) {
super(name, name, null, scalarType);
this.parsedFormula = parsedFormula;
this.aggregate = aggregate;
this.asTarget = asTarget;
this.alias = alias;
}
@Override
public String toString() {
return "DynamicPropertyFormula[" + parsedFormula + "]";
}
@Override
public boolean isAggregation() {
return aggregate;
}
@Override
public void load(SqlBeanLoad sqlBeanLoad) {
try {
Object value = scalarType.read(sqlBeanLoad.ctx().getDataReader());
if (asTarget != null) {
sqlBeanLoad.load(asTarget, value);
}
} catch (Exception e) {
throw new PersistenceException("Error loading on " + fullName, e);
}
}
@Override
public void appendSelect(DbSqlContext ctx, boolean subQuery) {
ctx.appendParseSelect(parsedFormula, alias);
}
}
@@ -0,0 +1,70 @@
package io.ebeaninternal.server.deploy;
import io.ebeaninternal.server.query.SqlJoinType;
import io.ebeaninternal.server.query.SqlTreeProperty;
import io.ebeaninternal.server.type.ScalarType;
import java.util.List;
/**
* Abstract base for dynamic properties.
*/
abstract class DynamicPropertyBase implements SqlTreeProperty {
final String name;
final String fullName;
final String elPrefix;
final ScalarType<?> scalarType;
public DynamicPropertyBase(String name, String fullName, String elPrefix, ScalarType<?> scalarType) {
this.name = name;
this.fullName = fullName;
this.elPrefix = elPrefix;
this.scalarType = scalarType;
}
@Override
public String getName() {
return name;
}
@Override
public String getFullBeanName() {
return fullName;
}
@Override
public boolean isId() {
return false;
}
@Override
public boolean isEmbedded() {
return false;
}
@Override
public String getElPrefix() {
return elPrefix;
}
@Override
public ScalarType<?> getScalarType() {
return scalarType;
}
@Override
public void buildRawSqlSelectChain(String prefix, List<String> selectChain) {
// do nothing, only for RawSql
}
@Override
public void loadIgnore(DbReadContext ctx) {
scalarType.loadIgnore(ctx.getDataReader());
}
@Override
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
// do not add to from usually
}
}
@@ -0,0 +1,156 @@
package io.ebeaninternal.server.deploy;
import io.ebeaninternal.server.el.ElPropertyDeploy;
import io.ebeaninternal.server.query.SqlTreeProperty;
import io.ebeaninternal.server.type.ScalarType;
import java.sql.Types;
class FormulaPropertyPath {
private static final String[] AGG_FUNCTIONS = {"count", "max", "min", "avg"};
private static final String DISTINCT_ = "distinct ";
private final BeanDescriptor<?> descriptor;
private final String formula;
private final String outerFunction;
private final String internalExpression;
private boolean countDistinct;
private String cast;
private String alias;
FormulaPropertyPath(BeanDescriptor<?> descriptor, String formula) {
this.descriptor = descriptor;
this.formula = formula;
int openBracket = formula.indexOf('(');
int closeBracket = formula.lastIndexOf(')');
if (openBracket == -1 || closeBracket == -1) {
throw new IllegalStateException("Unable to parse formula [" + formula + "]");
}
outerFunction = formula.substring(0, openBracket).trim();
internalExpression = trimDistinct(formula.substring(openBracket+1, closeBracket));
if (closeBracket < formula.length() -1) {
// ::CastType as foo
String suffix = formula.substring(closeBracket+1).trim();
parseSuffix(suffix);
}
}
private void parseSuffix(String suffix) {
String[] split = suffix.split(" ");
if (split.length == 1) {
if (split[0].startsWith("::")) {
cast = split[0].substring(2);
} else {
alias = split[0];
}
} else if (split.length == 2) {
cast = "as".equals(split[0]) ? null : split[0].substring(2);
alias = split[1];
} else if (split.length == 3) {
cast = split[0].substring(2);
alias = split[2];
}
}
private String trimDistinct(String propertyName) {
if (propertyName.startsWith(DISTINCT_)) {
countDistinct = true;
return propertyName.substring(DISTINCT_.length());
} else {
return propertyName;
}
}
String outerFunction() {
return outerFunction;
}
String internalExpression() {
return internalExpression;
}
String cast() {
return cast;
}
String alias() {
return alias;
}
SqlTreeProperty build() {
DeployPropertyParser parser = descriptor.parser().setCatchFirst(true);
String parsed = parser.parse(internalExpression);
ElPropertyDeploy firstProp = parser.getFirstProp();
ScalarType<?> scalarType;
if (cast != null) {
scalarType = descriptor.getScalarType(cast);
if (scalarType == null) {
throw new IllegalStateException("Unable to find scalarType for cast of ["+cast+"] on formula [" + formula + "] for type " + descriptor);
}
} else if (isCount()) {
scalarType = descriptor.getScalarType(Types.BIGINT);
} else if (isConcat()) {
scalarType = descriptor.getScalarType(Types.VARCHAR);
} else {
// determine scalarType based on first property found by parser
if (firstProp != null) {
scalarType = firstProp.getBeanProperty().getScalarType();
} else {
throw new IllegalStateException("unable to determine scalarType of formula [" + formula + "] for type " + descriptor + " - maybe use a cast like ::String ?");
}
}
String logicalName = (alias == null) ? formula : alias;
BeanProperty targetProperty = null;
if (alias != null) {
targetProperty = descriptor._findBeanProperty(alias);
}
String parsedAggregation = buildFormula(parsed);
return new DynamicPropertyAggregationFormula(logicalName, scalarType, parsedAggregation, isAggregate(), targetProperty, alias);
}
private boolean isAggregate() {
for (String aggFunction : AGG_FUNCTIONS) {
if (aggFunction.equals(outerFunction)) {
return true;
}
}
return false;
}
private String buildFormula(String parsed) {
if (countDistinct) {
return "count(distinct " + parsed + ")";
} else {
return outerFunction + "(" + parsed + ")";
}
}
private boolean isCount() {
return outerFunction.equals("count");
}
private boolean isConcat() {
return outerFunction.equals("concat");
}
}
@@ -25,11 +25,14 @@ import io.ebeaninternal.server.deploy.ChainedBeanPersistListener;
import io.ebeaninternal.server.deploy.ChainedBeanPostConstructListener;
import io.ebeaninternal.server.deploy.ChainedBeanPostLoad;
import io.ebeaninternal.server.deploy.ChainedBeanQueryAdapter;
import io.ebeaninternal.server.deploy.DeployPropertyParserMap;
import io.ebeaninternal.server.deploy.IndexDefinition;
import io.ebeaninternal.server.deploy.InheritInfo;
import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.deploy.parse.DeployBeanInfo;
import io.ebeaninternal.server.idgen.UuidIdGenerator;
import io.ebeaninternal.server.idgen.UuidV1IdGenerator;
import io.ebeaninternal.server.idgen.UuidV1RndIdGenerator;
import io.ebeaninternal.server.idgen.UuidV4IdGenerator;
import io.ebeaninternal.server.rawsql.SpiRawSql;
import javax.persistence.Entity;
@@ -844,8 +847,22 @@ public class DeployBeanDescriptor<T> {
*/
public void setUuidGenerator() {
this.idType = IdType.EXTERNAL;
this.idGeneratorName = UuidIdGenerator.AUTO_UUID;
this.idGenerator = UuidIdGenerator.INSTANCE;
this.idGeneratorName = PlatformIdGenerator.AUTO_UUID;
switch (serverConfig.getUuidVersion()) {
case VERSION1:
this.idGenerator = UuidV1IdGenerator.getInstance(serverConfig.getUuidStateFile());
break;
case VERSION1RND:
this.idGenerator = UuidV1RndIdGenerator.INSTANCE;
break;
case VERSION4:
default:
this.idGenerator = UuidV4IdGenerator.INSTANCE;
break;
}
}
/**
@@ -1152,4 +1169,38 @@ public class DeployBeanDescriptor<T> {
}
namedRawSql.put(name, rawSql);
}
/**
* Parse the aggregation formula into expressions with table alias placeholders.
*/
public String parse(String aggregation) {
return new Parser(this).parse(aggregation);
}
/**
* Parser for top level properties into EL expressions (table alias placeholders).
*/
private static class Parser extends DeployPropertyParserMap {
private final DeployBeanDescriptor<?> descriptor;
Parser(DeployBeanDescriptor<?> descriptor) {
super(null);
this.descriptor = descriptor;
}
public String getDeployWord(String expression) {
return descriptor.getDeployWord(expression);
}
}
private String getDeployWord(String expression) {
if (expression.charAt(expression.length() - 1) == '(') {
return null;
}
// use 'current' table alias - refer BeanProperty appendSelect() for aggregation
DeployBeanProperty property = propMap.get(expression);
return (property == null) ? null : "${ta}." + property.getDbColumn();
}
}
@@ -175,6 +175,7 @@ public class DeployBeanProperty {
private String aggregationPrefix;
private String aggregation;
private String aggregationParsed;
private String sqlFormulaSelect;
private String sqlFormulaJoin;
@@ -615,10 +616,23 @@ public class DeployBeanProperty {
return aggregation != null;
}
public String getAggregation() {
/**
* Get the raw/logical aggregation formula.
*/
public String getRawAggregation() {
return aggregation;
}
/**
* Get the parsed aggregation formula with table alias placeholders.
*/
public String parseAggregation() {
if (aggregation != null) {
aggregationParsed = desc.parse(aggregation);
}
return aggregationParsed;
}
public void setAggregation(String aggregation) {
this.aggregation = aggregation;
this.dbRead = true;
@@ -629,9 +643,9 @@ public class DeployBeanProperty {
/**
* Set the path to the aggregation.
*/
public void setAggregationPrefix(String aggregationPrefix) {
this.aggregationPrefix = aggregationPrefix;
this.aggregation = aggregation.replace(aggregationPrefix, "u1");
public void setAggregationPrefix(String prefix) {
this.aggregationPrefix = prefix;
this.aggregation = (prefix == null) ? aggregation : aggregation.replace(aggregationPrefix, "u1");
}
public String getElPrefix() {
@@ -644,7 +658,7 @@ public class DeployBeanProperty {
public String getElPlaceHolder() {
if (aggregation != null) {
return aggregation;
return aggregationParsed;
} else if (sqlFormulaSelect != null) {
return sqlFormulaSelect;
} else {
@@ -97,6 +97,9 @@ public abstract class AnnotationParser extends AnnotationBase {
* can be applied to DDL generation.
*/
protected boolean isEbeanValidationGroups(Class<?>[] groups) {
if (!util.isUseJavaxValidationNotNull()) {
return false;
}
if (groups.length == 0
|| groups.length == 1 && javax.validation.groups.Default.class.isAssignableFrom(groups[0])) {
return true;
@@ -60,6 +60,8 @@ public class DeployUtil {
private final Encryptor bytesEncryptor;
private final boolean useJavaxValidationNotNull;
public DeployUtil(TypeManager typeMgr, ServerConfig serverConfig) {
this.typeManager = typeMgr;
@@ -70,6 +72,7 @@ public class DeployUtil {
Encryptor be = serverConfig.getEncryptor();
this.bytesEncryptor = be != null ? be : new SimpleAesEncryptor();
this.useJavaxValidationNotNull = serverConfig.isUseJavaxValidationNotNull();
}
public TypeManager getTypeManager() {
@@ -294,4 +297,7 @@ public class DeployUtil {
return type.equals(String.class);
}
public boolean isUseJavaxValidationNotNull() {
return useJavaxValidationNotNull;
}
}
@@ -0,0 +1,45 @@
package io.ebeaninternal.server.dto;
import io.ebeaninternal.metric.QueryPlanCollector;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Manages the query plans for a given DTO bean type.
*/
public class DtoBeanDescriptor<T> {
private final Map<Object, DtoQueryPlan> plans = new ConcurrentHashMap<>();
private final Class<T> dtoType;
private final DtoMeta meta;
DtoBeanDescriptor(Class<T> dtoType, DtoMeta meta) {
this.dtoType = dtoType;
this.meta = meta;
}
public Class<T> getType() {
return dtoType;
}
public DtoQueryPlan getQueryPlan(Object planKey) {
return plans.get(planKey);
}
public DtoQueryPlan buildPlan(DtoMappingRequest request) {
return meta.match(request);
}
public void putQueryPlan(Object planKey, DtoQueryPlan plan) {
plans.put(planKey, plan);
}
public void collectStats(QueryPlanCollector collector) {
for (DtoQueryPlan plan : plans.values()) {
plan.collectStats(collector);
}
}
}
@@ -0,0 +1,53 @@
package io.ebeaninternal.server.dto;
import io.ebean.meta.MetaQueryMetric;
import io.ebeaninternal.metric.MetricFactory;
import io.ebeaninternal.metric.QueryPlanCollector;
import io.ebeaninternal.server.type.TypeManager;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Manages all the DTO bean descriptors.
*/
public class DtoBeanManager {
private final TypeManager typeManager;
private final Map<Class, DtoBeanDescriptor> descriptorMap = new ConcurrentHashMap<>();
public DtoBeanManager(TypeManager typeManager) {
this.typeManager = typeManager;
}
/**
* Return the descriptor for the given DTO bean class.
*/
@SuppressWarnings("unchecked")
public <T> DtoBeanDescriptor<T> getDescriptor(Class<T> dtoType) {
return descriptorMap.computeIfAbsent(dtoType, this::createDescriptor);
}
private <T> DtoBeanDescriptor createDescriptor(Class<T> dtoType) {
try {
DtoMeta meta = new DtoMetaBuilder(dtoType, typeManager).build();
return new DtoBeanDescriptor<>(dtoType, meta);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
public List<MetaQueryMetric> collectStats(boolean reset) {
QueryPlanCollector collector = MetricFactory.get().createCollector(reset);
for (DtoBeanDescriptor value : descriptorMap.values()) {
value.collectStats(collector);
}
return collector.complete();
}
}
@@ -0,0 +1,23 @@
package io.ebeaninternal.server.dto;
/**
* A column in the resultSet that we want to map to a bean property.
*/
public class DtoColumn {
private final String label;
public DtoColumn(String label) {
this.label = label;
}
public String getLabel() {
return label;
}
@Override
public String toString() {
return label;
}
}
@@ -0,0 +1,49 @@
package io.ebeaninternal.server.dto;
import io.ebeaninternal.api.SpiDtoQuery;
import io.ebeaninternal.metric.MetricFactory;
import io.ebeaninternal.metric.QueryPlanMetric;
/**
* Request to map a resultSet columns for a query into a DTO bean.
*/
public class DtoMappingRequest {
private final Class type;
private final String label;
private final String sql;
private final boolean relaxedMode;
private final DtoColumn[] columnMeta;
public DtoMappingRequest(SpiDtoQuery query, String sql, DtoColumn[] columnMeta) {
this.type = query.getType();
this.label = query.getLabel();
this.sql = sql;
this.relaxedMode = query.isRelaxedMode();
this.columnMeta = columnMeta;
}
public DtoColumn[] getColumnMeta() {
return columnMeta;
}
public boolean isRelaxedMode() {
return relaxedMode;
}
public String getLabel() {
return label;
}
public String getSql() {
return sql;
}
public QueryPlanMetric createMetric() {
return MetricFactory.get().createQueryPlanMetric(type, label, sql);
}
}
@@ -0,0 +1,132 @@
package io.ebeaninternal.server.dto;
import io.ebean.util.StringHelper;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Holds property and constructor meta data for a given DTO bean type.
*
* Uses this to map a mapping request (columns) to a 'query plan' (constructor and setters).
*/
class DtoMeta {
private final Class<?> dtoType;
private final Map<String, DtoMetaProperty> propMap = new LinkedHashMap<>();
private final Map<Integer, DtoMetaConstructor> constructorMap = new LinkedHashMap<>();
private final DtoMetaConstructor defaultConstructor;
private final DtoMetaConstructor maxArgConstructor;
DtoMeta(Class<?> dtoType, List<DtoMetaConstructor> constructors, List<DtoMetaProperty> properties) {
this.dtoType = dtoType;
for (DtoMetaProperty property : properties) {
propMap.put(property.getName().toUpperCase(), property);
}
int maxArg = 0;
DtoMetaConstructor defaultConstructor = null;
DtoMetaConstructor maxArgConstructor = null;
for (DtoMetaConstructor constructor : constructors) {
int args = constructor.getArgCount();
constructorMap.put(args, constructor);
if (args == 0) {
defaultConstructor = constructor;
} else if (args > maxArg) {
maxArgConstructor = constructor;
maxArg = args;
}
}
this.defaultConstructor = defaultConstructor;
this.maxArgConstructor = maxArgConstructor;
}
public DtoQueryPlan match(DtoMappingRequest request) {
DtoColumn[] cols = request.getColumnMeta();
int colLen = cols.length;
DtoMetaConstructor constructor = constructorMap.get(colLen);
if (constructor != null) {
return new DtoQueryPlanConstructor(request, constructor);
}
if (maxArgConstructor != null && colLen > maxArgConstructor.getArgCount()) {
// maxArgConst + setters
return matchMaxArgPlusSetters(request);
}
if (defaultConstructor != null) {
return matchSetters(request);
}
String msg = "Unable to map the resultSet columns " + Arrays.toString(cols)
+ " to the bean type ["+dtoType+"] as the number of columns in the resultSet is less than the constructor"
+ " (and that there is no default constructor) ?";
throw new IllegalStateException(msg);
}
private DtoQueryPlanConPlus matchMaxArgPlusSetters(DtoMappingRequest request) {
int firstOnes = maxArgConstructor.getArgCount();
DtoColumn[] cols = request.getColumnMeta();
DtoReadSet[] setterProps = new DtoReadSet[cols.length - firstOnes];
int pos = 0;
for (int i = firstOnes; i < cols.length; i++) {
String label = cols[i].getLabel();
DtoReadSet property = findProperty(label);
if (property == null || property.isReadOnly()) {
if (request.isRelaxedMode()) {
property = DtoReadSetColumnSkip.INSTANCE;
} else {
throw new IllegalStateException("Unable to map DB column " + cols[i] + " to a property with a setter method on " + dtoType);
}
}
setterProps[pos++] = property;
}
return new DtoQueryPlanConPlus(request, maxArgConstructor, setterProps);
}
private DtoQueryPlan matchSetters(DtoMappingRequest request) {
DtoColumn[] cols = request.getColumnMeta();
DtoReadSet[] setterProps = new DtoReadSet[cols.length];
for (int i = 0; i < cols.length; i++) {
String label = cols[i].getLabel();
DtoReadSet property = findProperty(label);
if (property == null || property.isReadOnly()) {
if (request.isRelaxedMode()) {
property = DtoReadSetColumnSkip.INSTANCE;
} else {
throw new IllegalStateException("Unable to map DB column " + cols[i] + " to a property with a setter method on " + dtoType);
}
}
setterProps[i] = property;
}
return new DtoQueryPlanConSetter(request, defaultConstructor, setterProps);
}
private DtoReadSet findProperty(String label) {
String upperLabel = label.toUpperCase();
DtoMetaProperty property = propMap.get(upperLabel);
if (property == null) {
upperLabel = StringHelper.replaceString(upperLabel, "_", "");
property = propMap.get(upperLabel);
}
return property;
}
}
@@ -0,0 +1,77 @@
package io.ebeaninternal.server.dto;
import io.ebeaninternal.server.type.TypeManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
/**
* Build the DtoMeta for a bean.
* <p>
* Use TypeManager to map bean property types to ScalarTypes.
*/
class DtoMetaBuilder {
private static final Logger log = LoggerFactory.getLogger(DtoMetaBuilder.class);
private final TypeManager typeManager;
private final Class<?> dtoType;
private final List<DtoMetaProperty> properties = new ArrayList<>();
private final List<DtoMetaConstructor> constructorList = new ArrayList<>();
DtoMetaBuilder(Class<?> dtoType, TypeManager typeManager) {
this.dtoType = dtoType;
this.typeManager = typeManager;
}
public DtoMeta build() throws IntrospectionException {
readConstructors();
readProperties();
return new DtoMeta(dtoType, constructorList, properties);
}
private void readProperties() throws IntrospectionException {
BeanInfo beanInfo = Introspector.getBeanInfo(dtoType);
for (PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors()) {
if (include(propertyDescriptor)) {
try {
properties.add(new DtoMetaProperty(typeManager, propertyDescriptor, dtoType));
} catch (Exception e) {
log.debug("exclude on " + dtoType + " property " + propertyDescriptor.getName(), e);
}
}
}
}
private void readConstructors() {
Constructor<?>[] constructors = dtoType.getConstructors();
for (Constructor<?> constructor : constructors) {
try {
constructorList.add(new DtoMetaConstructor(typeManager, constructor, dtoType));
} catch (Exception e) {
// we don't want that constructor
log.debug("exclude on " + dtoType + " constructor " + constructor, e);
}
}
}
private boolean include(PropertyDescriptor property) {
return !property.getName().equals("class");
}
}
@@ -0,0 +1,67 @@
package io.ebeaninternal.server.dto;
import io.ebeaninternal.server.type.DataReader;
import io.ebeaninternal.server.type.ScalarType;
import io.ebeaninternal.server.type.TypeManager;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.sql.SQLException;
class DtoMetaConstructor {
private final Class<?>[] types;
private final MethodHandle handle;
private final ScalarType<?>[] scalarTypes;
DtoMetaConstructor(TypeManager typeManager, Constructor<?> constructor, Class<?> someClass) throws NoSuchMethodException, IllegalAccessException {
this.types = constructor.getParameterTypes();
this.scalarTypes = new ScalarType[types.length];
for (int i = 0; i < types.length; i++) {
scalarTypes[i] = typeManager.getScalarType(types[i]);
}
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
this.handle = lookup.findConstructor(someClass, typeFor(types));
}
private MethodType typeFor(Class<?>[] types) {
return MethodType.methodType(void.class, types);
}
Class<?>[] getTypes() {
return types;
}
int getArgCount() {
return types.length;
}
Object defaultConstructor() {
try {
return handle.invokeWithArguments();
} catch (Throwable e) {
throw new RuntimeException("Unexpected error invoking constructor", e);
}
}
public Object process(DataReader dataReader) throws SQLException {
Object[] values = new Object[scalarTypes.length];
for (int i = 0; i < scalarTypes.length; i++) {
values[i] = scalarTypes[i].read(dataReader);
}
return invoke(values);
}
private Object invoke(Object... args) {
try {
return handle.invokeWithArguments(args);
} catch (Throwable e) {
throw new RuntimeException("Unexpected error invoking constructor", e);
}
}
}
@@ -0,0 +1,68 @@
package io.ebeaninternal.server.dto;
import io.ebeaninternal.server.type.DataReader;
import io.ebeaninternal.server.type.ScalarType;
import io.ebeaninternal.server.type.TypeManager;
import java.beans.PropertyDescriptor;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.sql.SQLException;
class DtoMetaProperty implements DtoReadSet {
private final Class<?> dtoType;
private final String name;
private final MethodHandle setter;
private final ScalarType<?> scalarType;
DtoMetaProperty(TypeManager typeManager, PropertyDescriptor descriptor, Class<?> dtoType) throws IllegalAccessException, NoSuchMethodException {
this.dtoType = dtoType;
this.name = descriptor.getName();
Method writeMethod = descriptor.getWriteMethod();
if (writeMethod != null) {
Class<?> propertyType = descriptor.getPropertyType();
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
this.setter = lookup.findVirtual(dtoType, writeMethod.getName(), MethodType.methodType(void.class, propertyType));
this.scalarType = typeManager.getScalarType(propertyType);
} else {
this.scalarType = null;
this.setter = null;
}
}
String getName() {
return name;
}
@Override
public boolean isReadOnly() {
return scalarType == null;
}
@Override
public void readSet(Object bean, DataReader dataReader) throws SQLException {
Object value = scalarType.read(dataReader);
invoke(bean, value);
}
private void invoke(Object instance, Object arg) {
try {
setter.invoke(instance, arg);
} catch (Throwable e) {
throw new RuntimeException("Error calling setter for property " + fullname() + " with arg: " + arg, e);
}
}
private String fullname() {
return dtoType.getName() + "." + name;
}
}
@@ -0,0 +1,27 @@
package io.ebeaninternal.server.dto;
import io.ebeaninternal.metric.QueryPlanCollector;
import io.ebeaninternal.server.type.DataReader;
import java.sql.SQLException;
/**
* Knows how to read and map rows into a Bean.
*/
public interface DtoQueryPlan {
/**
* Read the row data and return the DTO bean.
*/
Object readRow(DataReader dataReader) throws SQLException;
/**
* Add an event to the query execution statistics.
*/
void collect(long exeMicros, int rows);
/**
* Collect the query plan statistics.
*/
void collectStats(QueryPlanCollector collector);
}
@@ -0,0 +1,28 @@
package io.ebeaninternal.server.dto;
import io.ebeaninternal.metric.QueryPlanCollector;
import io.ebeaninternal.metric.QueryPlanMetric;
import io.ebeaninternal.metric.TimedMetric;
abstract class DtoQueryPlanBase implements DtoQueryPlan {
private final QueryPlanMetric planMetric;
private final TimedMetric metric;
DtoQueryPlanBase(DtoMappingRequest request) {
this.planMetric = request.createMetric();
this.metric = planMetric.getMetric();
}
@Override
public void collect(long exeTime, int rows) {
metric.add(exeTime, rows);
}
@Override
public void collectStats(QueryPlanCollector collector) {
planMetric.collect(collector);
}
}
@@ -0,0 +1,32 @@
package io.ebeaninternal.server.dto;
import io.ebeaninternal.server.type.DataReader;
import java.sql.SQLException;
/**
* Plan based on Constructor plus some setter methods.
*/
class DtoQueryPlanConPlus extends DtoQueryPlanBase {
private final DtoMetaConstructor maxArgConstructor;
private final DtoReadSet[] setterProps;
DtoQueryPlanConPlus(DtoMappingRequest request, DtoMetaConstructor maxArgConstructor, DtoReadSet[] setterProps) {
super(request);
this.maxArgConstructor = maxArgConstructor;
this.setterProps = setterProps;
}
@Override
public Object readRow(DataReader dataReader) throws SQLException {
Object bean = maxArgConstructor.process(dataReader);
for (DtoReadSet setterProp : setterProps) {
setterProp.readSet(bean, dataReader);
}
return bean;
}
}
@@ -0,0 +1,32 @@
package io.ebeaninternal.server.dto;
import io.ebeaninternal.server.type.DataReader;
import java.sql.SQLException;
/**
* Plan based on default constructor and setter methods.
*/
class DtoQueryPlanConSetter extends DtoQueryPlanBase {
private final DtoMetaConstructor defaultConstructor;
private final DtoReadSet[] setterProps;
DtoQueryPlanConSetter(DtoMappingRequest request, DtoMetaConstructor defaultConstructor, DtoReadSet[] setterProps) {
super(request);
this.defaultConstructor = defaultConstructor;
this.setterProps = setterProps;
}
@Override
public Object readRow(DataReader dataReader) throws SQLException {
Object bean = defaultConstructor.defaultConstructor();
for (DtoReadSet setterProp : setterProps) {
setterProp.readSet(bean, dataReader);
}
return bean;
}
}
@@ -0,0 +1,24 @@
package io.ebeaninternal.server.dto;
import io.ebeaninternal.server.type.DataReader;
import java.sql.SQLException;
/**
* Plan based on mapping via single constructor only.
*/
class DtoQueryPlanConstructor extends DtoQueryPlanBase {
private final DtoMetaConstructor constructor;
DtoQueryPlanConstructor(DtoMappingRequest request, DtoMetaConstructor constructor) {
super(request);
this.constructor = constructor;
}
@Override
public Object readRow(DataReader dataReader) throws SQLException {
return constructor.process(dataReader);
}
}
@@ -0,0 +1,21 @@
package io.ebeaninternal.server.dto;
import io.ebeaninternal.server.type.DataReader;
import java.sql.SQLException;
/**
* Read and set a property value.
*/
public interface DtoReadSet {
/**
* Read the value from the dataReader and set it to the bean.
*/
void readSet(Object bean, DataReader dataReader) throws SQLException;
/**
* Return true if this maps to a read only property (no setter method).
*/
boolean isReadOnly();
}
@@ -0,0 +1,21 @@
package io.ebeaninternal.server.dto;
import io.ebeaninternal.server.type.DataReader;
/**
* Placeholder to skip reading a column that isn't mapped to a bean property.
*/
class DtoReadSetColumnSkip implements DtoReadSet {
static final DtoReadSet INSTANCE = new DtoReadSetColumnSkip();
@Override
public void readSet(Object bean, DataReader dataReader) {
dataReader.incrementPos(1);
}
@Override
public boolean isReadOnly() {
return false;
}
}
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.expression;
import io.ebean.CacheMode;
import io.ebean.CountDistinctOrder;
import io.ebean.Expression;
import io.ebean.ExpressionFactory;
import io.ebean.ExpressionList;
@@ -493,6 +494,10 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
return query.setUseQueryCache(useCache);
}
public Query<T> setCountDistinct(CountDistinctOrder orderBy) {
return query.setCountDistinct(orderBy);
}
@Override
public Query<T> setUseDocStore(boolean useDocsStore) {
return query.setUseDocStore(useDocsStore);
@@ -40,6 +40,7 @@ class InExpression extends AbstractExpression {
private List<Object> values() {
List<Object> vals = new ArrayList<>(sourceValues.size());
for (Object sourceValue : sourceValues) {
assert sourceValue != null : "null is not allowed in in-queries";
NamedParamHelp.valueAdd(vals, sourceValue);
}
return vals;
@@ -66,7 +67,11 @@ class InExpression extends AbstractExpression {
@Override
public void addBindValues(SpiExpressionRequest request) {
for (Object value : bindValues) {
if (value == null) {
throw new NullPointerException("null values in 'in(...)' queries must be handled separately!");
}
}
ElPropertyValue prop = getElProp(request);
if (prop != null && !prop.isAssocId()) {
prop = null;
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.expression;
import io.ebean.CacheMode;
import io.ebean.CountDistinctOrder;
import io.ebean.Expression;
import io.ebean.ExpressionList;
import io.ebean.FetchPath;
@@ -792,6 +793,11 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
return exprList.setDisableReadAuditing();
}
@Override
public Query<T> setCountDistinct(CountDistinctOrder orderBy) {
return exprList.setCountDistinct(orderBy);
}
@Override
public ExpressionList<T> startsWith(String propertyName, String value) {
return exprList.startsWith(propertyName, value);
@@ -3,8 +3,8 @@ package io.ebeaninternal.server.grammer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.grammer.antlr.EQLLexer;
import io.ebeaninternal.server.grammer.antlr.EQLParser;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.BaseErrorListener;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
@@ -22,7 +22,7 @@ public class EqlParser {
*/
public static <T> void parse(String raw, SpiQuery<T> query) {
EQLLexer lexer = new EQLLexer(new ANTLRInputStream(raw));
EQLLexer lexer = new EQLLexer(CharStreams.fromString(raw));
CommonTokenStream tokens = new CommonTokenStream(lexer);
EQLParser parser = new EQLParser(tokens);
parser.addErrorListener(errorListener);
@@ -1,4 +1,4 @@
// Generated from /home/rob/github/ebean/src/test/resources/EQL.g4 by ANTLR 4.6
// Generated from /home/rob/github/ebean/src/test/resources/EQL.g4 by ANTLR 4.7
package io.ebeaninternal.server.grammer.antlr;
import org.antlr.v4.runtime.ParserRuleContext;
@@ -1,9 +1,8 @@
// Generated from /home/rob/github/ebean/src/test/resources/EQL.g4 by ANTLR 4.6
// Generated from /home/rob/github/ebean/src/test/resources/EQL.g4 by ANTLR 4.7
package io.ebeaninternal.server.grammer.antlr;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.Lexer;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.RuntimeMetaData;
import org.antlr.v4.runtime.Vocabulary;
import org.antlr.v4.runtime.VocabularyImpl;
@@ -16,7 +15,7 @@ import org.antlr.v4.runtime.dfa.DFA;
@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"})
public class EQLLexer extends Lexer {
static {
RuntimeMetaData.checkVersion("4.6", RuntimeMetaData.VERSION);
RuntimeMetaData.checkVersion("4.7", RuntimeMetaData.VERSION);
}
protected static final DFA[] _decisionToDFA;
@@ -33,6 +32,10 @@ public class EQLLexer extends Lexer {
T__52 = 53, T__53 = 54, T__54 = 55, T__55 = 56, T__56 = 57, INPUT_VARIABLE = 58, PATH_VARIABLE = 59,
BOOLEAN_LITERAL = 60, NUMBER_LITERAL = 61, DOUBLE = 62, INT = 63, ZERO = 64, STRING_LITERAL = 65,
WS = 66;
public static String[] channelNames = {
"DEFAULT_TOKEN_CHANNEL", "HIDDEN"
};
public static String[] modeNames = {
"DEFAULT_MODE"
};
@@ -104,12 +107,6 @@ public class EQLLexer extends Lexer {
}
@Override
public void recover(RecognitionException e) {
throw new RuntimeException("Error in grammer - " + e.getMessage());
}
public EQLLexer(CharStream input) {
super(input);
_interp = new LexerATNSimulator(this, _ATN, _decisionToDFA, _sharedContextCache);
@@ -130,6 +127,11 @@ public class EQLLexer extends Lexer {
return _serializedATN;
}
@Override
public String[] getChannelNames() {
return channelNames;
}
@Override
public String[] getModeNames() {
return modeNames;
@@ -141,7 +143,7 @@ public class EQLLexer extends Lexer {
}
public static final String _serializedATN =
"\3\u0430\ud6d1\u8206\uad2d\u4417\uaef1\u8d80\uaadd\2D\u0200\b\1\4\2\t" +
"\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\2D\u0200\b\1\4\2\t" +
"\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\4\n\t\n\4\13" +
"\t\13\4\f\t\f\4\r\t\r\4\16\t\16\4\17\t\17\4\20\t\20\4\21\t\21\4\22\t\22" +
"\4\23\t\23\4\24\t\24\4\25\t\25\4\26\t\26\4\27\t\27\4\30\t\30\4\31\t\31" +
@@ -177,22 +179,22 @@ public class EQLLexer extends Lexer {
";\37= ?!A\"C#E$G%I&K\'M(O)Q*S+U,W-Y.[/]\60_\61a\62c\63e\64g\65i\66k\67" +
"m8o9q:s;u<w=y>{?}@\177A\u0081B\u0083C\u0085D\3\2\t\5\2C\\aac|\6\2\62;" +
"C\\aac|\7\2\60\60\62;C\\aac|\3\2\62;\3\2\63;\3\2))\5\2\13\f\17\17\"\"" +
"\u020b\2\3\3\2\2\2\2\5\3\2\2\2\2\7\3\2\2\2\2\t\3\2\2\2\2\13\3\2\2\2\2" +
"\r\3\2\2\2\2\17\3\2\2\2\2\21\3\2\2\2\2\23\3\2\2\2\2\25\3\2\2\2\2\27\3" +
"\2\2\2\2\31\3\2\2\2\2\33\3\2\2\2\2\35\3\2\2\2\2\37\3\2\2\2\2!\3\2\2\2" +
"\2#\3\2\2\2\2%\3\2\2\2\2\'\3\2\2\2\2)\3\2\2\2\2+\3\2\2\2\2-\3\2\2\2\2" +
"/\3\2\2\2\2\61\3\2\2\2\2\63\3\2\2\2\2\65\3\2\2\2\2\67\3\2\2\2\29\3\2\2" +
"\2\2;\3\2\2\2\2=\3\2\2\2\2?\3\2\2\2\2A\3\2\2\2\2C\3\2\2\2\2E\3\2\2\2\2" +
"G\3\2\2\2\2I\3\2\2\2\2K\3\2\2\2\2M\3\2\2\2\2O\3\2\2\2\2Q\3\2\2\2\2S\3" +
"\2\2\2\2U\3\2\2\2\2W\3\2\2\2\2Y\3\2\2\2\2[\3\2\2\2\2]\3\2\2\2\2_\3\2\2" +
"\2\2a\3\2\2\2\2c\3\2\2\2\2e\3\2\2\2\2g\3\2\2\2\2i\3\2\2\2\2k\3\2\2\2\2" +
"m\3\2\2\2\2o\3\2\2\2\2q\3\2\2\2\2s\3\2\2\2\2u\3\2\2\2\2w\3\2\2\2\2y\3" +
"\2\2\2\2{\3\2\2\2\2}\3\2\2\2\2\177\3\2\2\2\2\u0081\3\2\2\2\2\u0083\3\2" +
"\2\2\2\u0085\3\2\2\2\3\u0087\3\2\2\2\5\u008e\3\2\2\2\7\u0090\3\2\2\2\t" +
"\u0092\3\2\2\2\13\u009b\3\2\2\2\r\u00a1\3\2\2\2\17\u00a7\3\2\2\2\21\u00aa" +
"\3\2\2\2\23\u00ac\3\2\2\2\25\u00b2\3\2\2\2\27\u00b8\3\2\2\2\31\u00bd\3" +
"\2\2\2\33\u00c1\3\2\2\2\35\u00c6\3\2\2\2\37\u00cc\3\2\2\2!\u00d3\3\2\2" +
"\2#\u00d9\3\2\2\2%\u00db\3\2\2\2\'\u00e1\3\2\2\2)\u00e6\3\2\2\2+\u00e9" +
"\2\u020b\2\3\3\2\2\2\2\5\3\2\2\2\2\7\3\2\2\2\2\t\3\2\2\2\2\13\3\2\2\2" +
"\2\r\3\2\2\2\2\17\3\2\2\2\2\21\3\2\2\2\2\23\3\2\2\2\2\25\3\2\2\2\2\27" +
"\3\2\2\2\2\31\3\2\2\2\2\33\3\2\2\2\2\35\3\2\2\2\2\37\3\2\2\2\2!\3\2\2" +
"\2\2#\3\2\2\2\2%\3\2\2\2\2\'\3\2\2\2\2)\3\2\2\2\2+\3\2\2\2\2-\3\2\2\2" +
"\2/\3\2\2\2\2\61\3\2\2\2\2\63\3\2\2\2\2\65\3\2\2\2\2\67\3\2\2\2\29\3\2" +
"\2\2\2;\3\2\2\2\2=\3\2\2\2\2?\3\2\2\2\2A\3\2\2\2\2C\3\2\2\2\2E\3\2\2\2" +
"\2G\3\2\2\2\2I\3\2\2\2\2K\3\2\2\2\2M\3\2\2\2\2O\3\2\2\2\2Q\3\2\2\2\2S" +
"\3\2\2\2\2U\3\2\2\2\2W\3\2\2\2\2Y\3\2\2\2\2[\3\2\2\2\2]\3\2\2\2\2_\3\2" +
"\2\2\2a\3\2\2\2\2c\3\2\2\2\2e\3\2\2\2\2g\3\2\2\2\2i\3\2\2\2\2k\3\2\2\2" +
"\2m\3\2\2\2\2o\3\2\2\2\2q\3\2\2\2\2s\3\2\2\2\2u\3\2\2\2\2w\3\2\2\2\2y" +
"\3\2\2\2\2{\3\2\2\2\2}\3\2\2\2\2\177\3\2\2\2\2\u0081\3\2\2\2\2\u0083\3" +
"\2\2\2\2\u0085\3\2\2\2\3\u0087\3\2\2\2\5\u008e\3\2\2\2\7\u0090\3\2\2\2" +
"\t\u0092\3\2\2\2\13\u009b\3\2\2\2\r\u00a1\3\2\2\2\17\u00a7\3\2\2\2\21" +
"\u00aa\3\2\2\2\23\u00ac\3\2\2\2\25\u00b2\3\2\2\2\27\u00b8\3\2\2\2\31\u00bd" +
"\3\2\2\2\33\u00c1\3\2\2\2\35\u00c6\3\2\2\2\37\u00cc\3\2\2\2!\u00d3\3\2" +
"\2\2#\u00d9\3\2\2\2%\u00db\3\2\2\2\'\u00e1\3\2\2\2)\u00e6\3\2\2\2+\u00e9" +
"\3\2\2\2-\u00ed\3\2\2\2/\u00f1\3\2\2\2\61\u00f4\3\2\2\2\63\u00fc\3\2\2" +
"\2\65\u00ff\3\2\2\2\67\u0104\3\2\2\29\u010b\3\2\2\2;\u0115\3\2\2\2=\u011d" +
"\3\2\2\2?\u0123\3\2\2\2A\u012b\3\2\2\2C\u0136\3\2\2\2E\u013f\3\2\2\2G" +
@@ -1,4 +1,4 @@
// Generated from /home/rob/github/ebean/src/test/resources/EQL.g4 by ANTLR 4.6
// Generated from /home/rob/github/ebean/src/test/resources/EQL.g4 by ANTLR 4.7
package io.ebeaninternal.server.grammer.antlr;
import org.antlr.v4.runtime.tree.ParseTreeListener;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,197 @@
package io.ebeaninternal.server.idgen;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Date;
import java.util.Enumeration;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* IdGenerator for java util UUID.
*
* It extends the UuidV1RndIdGenerator so that it can generate rfc4122 compliant Type 1 UUIDs.
*
* This generator produces real Type 1 UUIDs (best for sqlserver) - You should use this generator only,
* if you can guarantee, that mac addess is uniqe.
*/
public class UuidV1IdGenerator extends UuidV1RndIdGenerator {
private static final Logger logger = LoggerFactory.getLogger("io.ebean.IDGEN");
private final File stateFile;
private byte[] nodeId = null;
private boolean canSaveState = true;
private static final Map<File, UuidV1IdGenerator> INSTANCES = new ConcurrentHashMap<>();
/**
* Returns an instance for given file.
*/
public static UuidV1IdGenerator getInstance(String file) {
return INSTANCES.computeIfAbsent(new File(file), UuidV1IdGenerator::new);
}
/**
* Returns an instance for given file.
*/
public static UuidV1IdGenerator getInstance(File file) {
return INSTANCES.computeIfAbsent(file, UuidV1IdGenerator::new);
}
/**
* Returns an alternative node id - set with the 'ebean.uuid.nodeId' system property.
*/
private static byte[] getAlternativeNodeId() {
try {
String altNodeId = System.getProperty("ebean.uuid.nodeId");
if (altNodeId != null) {
String[] components = altNodeId.split("-");
if (components.length != 5) {
throw new IllegalArgumentException("Invalid nodeId string: " + altNodeId);
}
byte[] nodeId = new byte[6];
for (int i=0; i<5; i++) {
nodeId[i] = Byte.decode("0x"+components[i]).byteValue();
}
return nodeId;
}
} catch (SecurityException se) {
// ignore
}
return null;
}
/**
* Find hardware ID.
*/
private static byte[] getHardwareId() throws SocketException {
final Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
while (e.hasMoreElements()) {
NetworkInterface network = e.nextElement();
if (!network.isLoopback()) {
return network.getHardwareAddress();
}
}
return null;
}
/**
* Creates a new instance of UuidGenerator. Note that there should not be more
* than one instance per stateFile.
*/
private UuidV1IdGenerator(final File stateFile) {
super();
this.stateFile = stateFile;
try {
// See, if there is an alternative MAC address set.
nodeId = getAlternativeNodeId();
if (nodeId != null) {
logger.info("Using alternative MAC {} to generate Type 1 UUIDs", getNodeIdentifier());
} else {
nodeId = getHardwareId();
logger.info("Using MAC {} to generate Type 1 UUIDs", getNodeIdentifier());
}
if (nodeId == null) {
canSaveState = false;
// RFC 4.5 use random portion for node
nodeId = super.getNodeIdBytes();
logger.error("Have to fall back to random node identifier {} (Reason: No suitable network interface found)", getNodeIdentifier());
} else {
boolean flag = restoreState();
UUID uuid = nextId(null);
long ts = timeStamp.get();
ts -= UUID_EPOCH_OFFSET;
ts /= MILLIS_TO_UUID;
saveState();
logger.debug("RestoreState: {}, ClockSeq {}, Timestamp {}, uuid {}, stateFile: {})", flag, clockSeq.get(),
new Date(ts), uuid, stateFile);
}
} catch (IOException e) {
canSaveState = false;
// RFC 4.5 use random portion for node
nodeId = super.getNodeIdBytes();
logger.error("Have to fall back to random node identifier {} (Reason: {} )", getNodeIdentifier(), e.getMessage());
}
}
/**
* Returns the Node-identifier (=MAC address) as string
*/
public String getNodeIdentifier() {
if (nodeId == null) {
return "none";
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < nodeId.length; i++) {
sb.append(String.format("%02X%s", nodeId[i], (i < nodeId.length - 1) ? "-" : ""));
}
return sb.toString();
}
/**
* Restores the state from the state file.
*/
private boolean restoreState() throws IOException {
Properties prop = new Properties();
if (stateFile.exists()) {
try (InputStream is = new FileInputStream(stateFile)) {
prop.load(is);
}
}
if (getNodeIdentifier().equals(prop.getProperty("nodeId"))) {
try {
Integer seq = Integer.valueOf(prop.getProperty("clockSeq")) & 0x3FFF;
Long ts = Long.valueOf(prop.getProperty("timeStamp"));
clockSeq.set(seq);
timeStamp.set(ts);
logger.debug("Restored state from '{}'", stateFile);
return true;
} catch (NumberFormatException nfe) {
// nop
}
}
return false;
}
/**
* Saves the state to the state file;
*/
@Override
protected void saveState() {
if (!canSaveState) {
return;
}
Properties prop = new Properties();
prop.setProperty("nodeId", getNodeIdentifier());
prop.setProperty("clockSeq", String.valueOf(clockSeq.get()));
prop.setProperty("timeStamp", String.valueOf(timeStamp.get()));
try (OutputStream os = new FileOutputStream(stateFile)) {
prop.store(os, "ebean uuid state file");
logger.debug("Persisted state to '{}'", stateFile);
} catch (IOException e) {
logger.error("Could not persist uuid state to '{}'", stateFile, e);
}
}
@Override
protected byte[] getNodeIdBytes() {
return nodeId;
}
}
@@ -0,0 +1,179 @@
package io.ebeaninternal.server.idgen;
import io.ebean.Transaction;
import io.ebean.config.dbplatform.PlatformIdGenerator;
import java.security.SecureRandom;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* IdGenerator for (pseudo) type 1 UUIDs.
*
* This implementation generates a type 1 UUID according to
* https://tools.ietf.org/html/rfc4122.html#section-4.2
* but has no persistence storage. It generates a new random 47 bit node ID with every
* UUID.
*
* Use this, if you want randomness in your UUIDs but want to take advantage of index
* optimizations of the database. It may be good with AUTO_BINARY_OPTIMIZED and MySql.
*
* See: https://www.percona.com/blog/2014/12/19/store-uuid-optimized-way/
*/
public class UuidV1RndIdGenerator implements PlatformIdGenerator {
protected static final Logger logger = LoggerFactory.getLogger("io.ebean.IDGEN");
// UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00.
protected static final long UUID_EPOCH_OFFSET = 0x01B21DD213814000L;
// the resolution is 100ns
protected static final long MILLIS_TO_UUID = 10000;
public static final UuidV1RndIdGenerator INSTANCE = new UuidV1RndIdGenerator();
protected final AtomicInteger clockSeq = new AtomicInteger((int) (Math.random() * 0x3FFF));
private final SecureRandom numberGenerator = new SecureRandom();
protected AtomicLong timeStamp = new AtomicLong(currentUuidTime());
private AtomicLong nanoToMilliOffset = new AtomicLong(currentUuidTime());
/**
* Returns the uuid epoch.
*
* This is the number of 100ns intervals since 1582-10-15 00:00:00
*/
private static long currentUuidTime() {
return (System.currentTimeMillis() * MILLIS_TO_UUID) + UUID_EPOCH_OFFSET;
}
public UuidV1RndIdGenerator() {
computeNanoOffset();
}
/**
* Computes the internal offset between System.nanoTime() and System.currentTimeMillis().
*/
protected void computeNanoOffset() {
long currentTime = currentUuidTime();
long fromNanos = System.nanoTime() / 100L + nanoToMilliOffset.get();
long offset = currentTime - fromNanos;
if (Math.abs(offset) > MILLIS_TO_UUID * 1000 ) {
nanoToMilliOffset.addAndGet(offset);
}
}
/**
* Method to overwrite to save the state in a non volatile place.
*/
protected void saveState() {
}
/**
* returns a random 47 bit value according to https://tools.ietf.org/html/rfc4122.html#section-4.5
*/
protected byte[] getNodeIdBytes() {
byte[] idBytes = new byte[6];
numberGenerator.nextBytes(idBytes);
idBytes[0] |= 0x01; // set multicast bit.
return idBytes;
}
/**
* Return UUID from UUID.randomUUID();
*/
@Override
public UUID nextId(Transaction t) {
long current = System.nanoTime() / 100L + nanoToMilliOffset.get();
long delta;
int seq;
do {
seq = clockSeq.get();
while (true) {
long last = timeStamp.get();
delta = current - last;
if (delta < -10000 * 20000) {
logger.info("Clock skew of {} ms detected", delta / -10000);
// The clock was adjusted back about 2 seconds, or we were generating a lot of ids too fast
// if so, we try to set the current as last and also increment the clockSeq.
synchronized (this) {
if (clockSeq.compareAndSet(seq, seq + 1)) {
timeStamp.set(current);
saveState();
computeNanoOffset();
} else {
continue;
}
}
}
// If current is in the future (most of the cases) try to set it.
if (delta > 0 && timeStamp.compareAndSet(last, current)) {
break;
} else if (timeStamp.compareAndSet(last, last + 1)) {
// here we go, if we pull IDs too fast.
current = last + 1;
break;
}
}
// verify, if this timestamp is for this clock sequence
} while (seq != clockSeq.get());
// save state every 60 seconds
if (delta > 60000 * 10000) {
saveState();
computeNanoOffset();
}
long msb = current << 32; // time low
msb |= (current & 0xFFFF00000000L) >> 16; // time mid
msb |= 0x1000 | ((current >> 48) & 0x0FFF); // time hi and version 1
byte[] idBytes = getNodeIdBytes();
long lsb = 0;
for (int i = 0; i < 6; i++) {
lsb = (lsb << 8) | (idBytes[i] & 0xff);
}
// RFC 4.1.1. Variant: is set with bits 10xx
seq = seq & 0x3FFF | 0x8000;
lsb |= (long) seq << 48;
return new UUID(msb, lsb);
}
/**
* Returns "uuid".
*/
@Override
public String getName() {
return "uuid";
}
/**
* Returns false.
*/
@Override
public boolean isDbSequence() {
return false;
}
/**
* Ignored for UUID as not required as a performance optimisation.
*/
@Override
public void preAllocateIds(int allocateSize) {
// ignored
}
}
@@ -7,10 +7,14 @@ import java.util.UUID;
/**
* IdGenerator for java util UUID.
*
* This generator generates a 60bit random UUID according to
* https://tools.ietf.org/html/rfc4122.html#section-4.4
* Use this generator if you want truly random UUIDs
*/
public class UuidIdGenerator implements PlatformIdGenerator {
public class UuidV4IdGenerator implements PlatformIdGenerator {
public static final UuidIdGenerator INSTANCE = new UuidIdGenerator();
public static final UuidV4IdGenerator INSTANCE = new UuidV4IdGenerator();
/**
* Return UUID from UUID.randomUUID();
@@ -85,16 +85,19 @@ public class BatchedBeanHolder {
// Note updates and deletes can result in many PreparedStatements
// if their where clauses differ via use of IS NOT NULL.
if (deletes != null && !deletes.isEmpty()) {
control.executeNow(deletes);
deletes.clear();
ArrayList<PersistRequest> bufferedDeletes = deletes;
deletes = new ArrayList<>();
control.executeNow(bufferedDeletes);
}
if (inserts != null && !inserts.isEmpty()) {
control.executeNow(inserts);
inserts.clear();
ArrayList<PersistRequest> bufferedInserts = inserts;
inserts = new ArrayList<>();
control.executeNow(bufferedInserts);
}
if (updates != null && !updates.isEmpty()) {
control.executeNow(updates);
updates.clear();
ArrayList<PersistRequest> bufferedUpdates = updates;
updates = new ArrayList<>();
control.executeNow(bufferedUpdates);
}
persistedBeans.clear();
}
@@ -1,6 +1,8 @@
package io.ebeaninternal.server.profile;
import io.ebeaninternal.metric.MetricFactory;
import io.ebeaninternal.metric.QueryPlanCollector;
import io.ebeaninternal.metric.QueryPlanMetric;
import io.ebeaninternal.metric.TimedMetric;
import io.ebeaninternal.metric.TimedMetricMap;
@@ -18,4 +20,14 @@ public class DMetricFactory implements MetricFactory {
public TimedMetric createTimedMetric(String name) {
return new DTimedMetric(name);
}
@Override
public QueryPlanMetric createQueryPlanMetric(Class<?> type, String label, String sql) {
return new DQueryPlanMetric(new DQueryPlanMeta(type, label, sql), createTimedMetric(label));
}
@Override
public QueryPlanCollector createCollector(boolean reset) {
return new DQueryPlanCollector(reset);
}
}
@@ -0,0 +1,33 @@
package io.ebeaninternal.server.profile;
import io.ebean.meta.MetaQueryMetric;
import io.ebeaninternal.metric.QueryPlanCollector;
import java.util.ArrayList;
import java.util.List;
class DQueryPlanCollector implements QueryPlanCollector {
private final boolean reset;
private final List<MetaQueryMetric> list = new ArrayList<>();
DQueryPlanCollector(boolean reset) {
this.reset = reset;
}
@Override
public boolean isReset() {
return reset;
}
@Override
public void add(MetaQueryMetric stats) {
list.add(stats);
}
@Override
public List<MetaQueryMetric> complete() {
return list;
}
}
@@ -0,0 +1,31 @@
package io.ebeaninternal.server.profile;
class DQueryPlanMeta {
private final Class<?> type;
private final String label;
private final String sql;
DQueryPlanMeta(Class<?> type, String label, String sql) {
this.type = type;
this.label = label;
this.sql = sql;
}
public Class<?> getType() {
return type;
}
public String getLabel() {
return label;
}
public String getSql() {
return sql;
}
@Override
public String toString() {
return "type:" + type + " label:" + label;
}
}

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