mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
58
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b594fb533 | ||
|
|
c0efd49c73 | ||
|
|
54ba1480eb | ||
|
|
4d8bc0a2ce | ||
|
|
e182190f96 | ||
|
|
8711ebc6f7 | ||
|
|
baa7242939 | ||
|
|
47e8d27212 | ||
|
|
70aed3906e | ||
|
|
70c3860a65 | ||
|
|
1ba21d5c43 | ||
|
|
2019bc7bf1 | ||
|
|
3cb52917e3 | ||
|
|
55a257a64c | ||
|
|
83ef204f3c | ||
|
|
a18cac035b | ||
|
|
873c14faaf | ||
|
|
e1af7c29eb | ||
|
|
c1c85b67c2 | ||
|
|
05b8adbf6e | ||
|
|
3ca4efc818 | ||
|
|
ec2c3b13d4 | ||
|
|
5a3e386b7c | ||
|
|
7e56a147be | ||
|
|
62cdfb3209 | ||
|
|
740532da4b | ||
|
|
130356cb3b | ||
|
|
ab84cfae52 | ||
|
|
ad21b7a3fd | ||
|
|
31581082d2 | ||
|
|
aa84dfe7d8 | ||
|
|
aad5b33db2 | ||
|
|
5801f7620d | ||
|
|
514416a1e0 | ||
|
|
8faa95adc1 | ||
|
|
ebaeafc11b | ||
|
|
98e014f8c8 | ||
|
|
78912d75e0 | ||
|
|
09786a7062 | ||
|
|
54530248a5 | ||
|
|
fdafa776e1 | ||
|
|
392eb2744d | ||
|
|
b642b3c644 | ||
|
|
0e1c815826 | ||
|
|
83b463040f | ||
|
|
aea7d937eb | ||
|
|
78628ffad0 | ||
|
|
56a742ad70 | ||
|
|
a4415b03ff | ||
|
|
e252f71792 | ||
|
|
18ca3403ac | ||
|
|
94a118d635 | ||
|
|
efe325034b | ||
|
|
0c368ed7b3 | ||
|
|
c1b022c123 | ||
|
|
021d57f4b2 | ||
|
|
11e180aa3f | ||
|
|
9e2ddc9f8c |
@@ -10,3 +10,4 @@ indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
spaces_around_operators = true
|
||||
max_line_length = 130
|
||||
|
||||
@@ -20,3 +20,4 @@ profiling/
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
*uuid.state
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.io.Serializable;
|
||||
/**
|
||||
* Holds a distinct value with it's count.
|
||||
* (Used with {@link Query#findSingleAttributeList()} and {@link Query#setCountDistinct(CountDistinctOrder)}.)
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*/
|
||||
public class CountedValue<A> implements Serializable {
|
||||
private static final long serialVersionUID = -2267971668356749695L;
|
||||
|
||||
private final A value;
|
||||
private final long count;
|
||||
|
||||
public CountedValue(A value, long count) {
|
||||
this.value = value;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public long getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public A getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return count + ": " + value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package io.ebean;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Query for performing native SQL queries that return DTO Bean's.
|
||||
* <p>
|
||||
* These beans are just normal classes. They must have public constructors
|
||||
* and setters.
|
||||
* <p>
|
||||
* Constructors with arguments are used if the number of constructor arguments
|
||||
* matches the number of columns in the resultSet.
|
||||
* </p>
|
||||
* <p>
|
||||
* If the number of columns in the resultSet is greater than the largest constructor
|
||||
* then the largest constructor is used for the first columns and remaining columns
|
||||
* are mapped by setter methods.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* // CustomerDto is just a 'bean like' class
|
||||
* // with public constructor(s) and public setter methods
|
||||
*
|
||||
* String sql = "select id, name from customer where name like :name and status_code = :status";
|
||||
*
|
||||
* List<CustomerDto> beans =
|
||||
* Ebean.findDto(CustomrDto.class, sql)
|
||||
* .setParameter("name", "Acme%")
|
||||
* .setParameter("status", "ACTIVE")
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public interface DtoQuery<T> {
|
||||
|
||||
/**
|
||||
* Execute the query returning a list.
|
||||
*/
|
||||
@Nonnull
|
||||
List<T> findList();
|
||||
|
||||
/**
|
||||
* Execute the query iterating a row at a time.
|
||||
* <p>
|
||||
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
|
||||
* </p>
|
||||
*/
|
||||
void findEach(Consumer<T> consumer);
|
||||
|
||||
/**
|
||||
* Execute the query iterating a row at a time with the ability to stop consuming part way through.
|
||||
* <p>
|
||||
* Returning false after processing a row stops the iteration through the query results.
|
||||
* </p>
|
||||
* <p>
|
||||
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
|
||||
* </p>
|
||||
*/
|
||||
void findEachWhile(Predicate<T> consumer);
|
||||
|
||||
/**
|
||||
* Execute the query returning a single bean.
|
||||
*/
|
||||
@Nullable
|
||||
T findOne();
|
||||
|
||||
/**
|
||||
* Execute the query returning an optional bean.
|
||||
*/
|
||||
@Nonnull
|
||||
Optional<T> findOneOrEmpty();
|
||||
|
||||
/**
|
||||
* The same as bind for named parameters.
|
||||
*/
|
||||
DtoQuery<T> setParameter(String name, Object value);
|
||||
|
||||
/**
|
||||
* The same as bind for positioned parameters.
|
||||
*/
|
||||
DtoQuery<T> setParameter(int position, Object value);
|
||||
|
||||
/**
|
||||
* Set the index of the first row of the results to return.
|
||||
*/
|
||||
DtoQuery<T> setFirstRow(int firstRow);
|
||||
|
||||
/**
|
||||
* Set the maximum number of query results to return.
|
||||
*/
|
||||
DtoQuery<T> setMaxRows(int maxRows);
|
||||
|
||||
/**
|
||||
* When resultSet columns are not able to be mapped to a bean property then instead of
|
||||
* throwing effectively skip reading that column.
|
||||
*/
|
||||
DtoQuery<T> setRelaxedMode();
|
||||
|
||||
/**
|
||||
* Set a label on the query to make it easier to identify queries related to query execution statistics.
|
||||
*
|
||||
* @param label A label that is unique to the DTO bean type.
|
||||
*/
|
||||
DtoQuery<T> setLabel(String label);
|
||||
|
||||
/**
|
||||
* Set a timeout on this query.
|
||||
* <p>
|
||||
* This will typically result in a call to setQueryTimeout() on a
|
||||
* preparedStatement. If the timeout occurs an exception will be thrown - this
|
||||
* will be a SQLException wrapped up in a PersistenceException.
|
||||
* </p>
|
||||
*
|
||||
* @param secs the query timeout limit in seconds. Zero means there is no limit.
|
||||
*/
|
||||
DtoQuery<T> setTimeout(int secs);
|
||||
|
||||
/**
|
||||
* A hint which for JDBC translates to the Statement.fetchSize().
|
||||
* <p>
|
||||
* Gives the JDBC driver a hint as to the number of rows that should be
|
||||
* fetched from the database when more rows are needed for ResultSet.
|
||||
* </p>
|
||||
*/
|
||||
DtoQuery<T> setBufferFetchSizeHint(int bufferFetchSizeHint);
|
||||
|
||||
}
|
||||
@@ -3,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>
|
||||
|
||||
@@ -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>.
|
||||
*
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
+6
-2
@@ -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
Reference in New Issue
Block a user