mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#350 - ENH: Add @History findVersions() query ... returning historic versions for a given bean using history view
This commit is contained in:
@@ -904,6 +904,15 @@ public interface EbeanServer {
|
||||
*/
|
||||
<T> void findEachWhile(Query<T> query, QueryEachWhileConsumer<T> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return versions of a @History entity bean.
|
||||
* <p>
|
||||
* Generally this query is expected to be a find by id or unique predicates query.
|
||||
* It will execute the query against the history returning the versions of the bean.
|
||||
* </p>
|
||||
*/
|
||||
<T> List<Version<T>> findVersions(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute a query returning a list of beans.
|
||||
* <p>
|
||||
|
||||
@@ -235,6 +235,15 @@ public interface ExpressionList<T> extends Serializable {
|
||||
*/
|
||||
PagedList<T> findPagedList(int pageIndex, int pageSize);
|
||||
|
||||
/**
|
||||
* Return versions of a @History entity bean.
|
||||
* <p>
|
||||
* Generally this query is expected to be a find by id or unique predicates query.
|
||||
* It will execute the query against the history returning the versions of the bean.
|
||||
* </p>
|
||||
*/
|
||||
List<Version<T>> findVersions();
|
||||
|
||||
/**
|
||||
* Add some filter predicate expressions to the many property.
|
||||
*/
|
||||
|
||||
@@ -720,6 +720,15 @@ public interface Query<T> extends Serializable {
|
||||
@Nullable
|
||||
T findUnique();
|
||||
|
||||
/**
|
||||
* Return versions of a @History entity bean.
|
||||
* <p>
|
||||
* Generally this query is expected to be a find by id or unique predicates query.
|
||||
* It will execute the query against the history returning the versions of the bean.
|
||||
* </p>
|
||||
*/
|
||||
List<Version<T>> findVersions();
|
||||
|
||||
/**
|
||||
* Return the count of entities this query should return.
|
||||
* <p>
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
|
||||
/**
|
||||
* Wraps a version of a @History bean.
|
||||
*/
|
||||
public class Version<T> {
|
||||
|
||||
/**
|
||||
* The version of the bean.
|
||||
*/
|
||||
T bean;
|
||||
|
||||
/**
|
||||
* The effective start date time of this version.
|
||||
*/
|
||||
Timestamp start;
|
||||
|
||||
/**
|
||||
* The effective end date time of this version.
|
||||
*/
|
||||
Timestamp end;
|
||||
|
||||
/**
|
||||
* Construct with bean and an effective date time range.
|
||||
*/
|
||||
public Version(T bean, Timestamp start, Timestamp end) {
|
||||
this.bean = bean;
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor - useful for JSON tools such as Jackson.
|
||||
*/
|
||||
public Version() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean instance for this version.
|
||||
*/
|
||||
public T getBean() {
|
||||
return bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bean instance for this version.
|
||||
*/
|
||||
public void setBean(T bean) {
|
||||
this.bean = bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the effective start date time of this version.
|
||||
*/
|
||||
public Timestamp getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the effective start date time of this version.
|
||||
*/
|
||||
public void setStart(Timestamp start) {
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the effective end date time of this version.
|
||||
*/
|
||||
public Timestamp getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the effective end date time of this version.
|
||||
*/
|
||||
public void setEnd(Timestamp end) {
|
||||
this.end = end;
|
||||
}
|
||||
}
|
||||
@@ -80,6 +80,11 @@ public class DatabasePlatform {
|
||||
*/
|
||||
protected DbIdentity dbIdentity = new DbIdentity();
|
||||
|
||||
/**
|
||||
* The history support for this database platform.
|
||||
*/
|
||||
protected DbHistorySupport historySupport;
|
||||
|
||||
/**
|
||||
* The JDBC type to map booleans to (by default).
|
||||
*/
|
||||
@@ -215,6 +220,20 @@ public class DatabasePlatform {
|
||||
this.dbEncrypt = dbEncrypt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the history support for this database platform.
|
||||
*/
|
||||
public DbHistorySupport getHistorySupport() {
|
||||
return historySupport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the history support for this database platform.
|
||||
*/
|
||||
public void setHistorySupport(DbHistorySupport historySupport) {
|
||||
this.historySupport = historySupport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mapping of JDBC to DB types.
|
||||
*
|
||||
@@ -442,17 +461,6 @@ public class DatabasePlatform {
|
||||
return disallowBatchOnCascade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the 'as of' predicate added for the given table alias.
|
||||
*
|
||||
* @param asOfTableAlias The table alias this predicate is added for
|
||||
* @param asOfSysPeriod The name of the 'sys_period' column used for effective date time range.
|
||||
* @return The predicate containing a single ? bind parameter which will be bound to the 'as at' timestamp value
|
||||
*/
|
||||
public String getAsOfPredicate(String asOfTableAlias, String asOfSysPeriod) {
|
||||
throw new RuntimeException("AsOf query not support of this database platform yet");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate and return the create sequence DDL.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
/**
|
||||
* History support for the database platform.
|
||||
*/
|
||||
public interface DbHistorySupport {
|
||||
|
||||
/**
|
||||
* Return the 'as of' predicate added for the given table alias.
|
||||
*
|
||||
* @param tableAlias The table alias this predicate is added for
|
||||
* @param sysPeriod The name of the 'sys_period' column used for effective date time range.
|
||||
* @return The predicate containing a single ? bind parameter which will be bound to the 'as at' timestamp value
|
||||
*/
|
||||
String getAsOfPredicate(String tableAlias, String sysPeriod);
|
||||
|
||||
/**
|
||||
* Return the column for the system period lower bound that will be included in findVersions() queries.
|
||||
*
|
||||
* @param tableAlias the table alias which will typically be 't0'
|
||||
* @param sysPeriod the name of the sys_period column
|
||||
*/
|
||||
String getSysPeriodLower(String tableAlias, String sysPeriod);
|
||||
|
||||
/**
|
||||
* Return the column for the system period upper bound that will be included in findVersions() queries.
|
||||
*
|
||||
* @param tableAlias the table alias which will typically be 't0'
|
||||
* @param sysPeriod the name of the sys_period column
|
||||
*/
|
||||
String getSysPeriodUpper(String tableAlias, String sysPeriod);
|
||||
|
||||
}
|
||||
@@ -21,6 +21,7 @@ public class Postgres8Platform extends DatabasePlatform {
|
||||
this.clobDbType = Types.VARCHAR;
|
||||
|
||||
this.dbEncrypt = new PostgresDbEncrypt();
|
||||
this.historySupport = new PostgresHistorySupport();
|
||||
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(false);
|
||||
this.dbIdentity.setIdType(IdType.SEQUENCE);
|
||||
@@ -47,30 +48,13 @@ public class Postgres8Platform extends DatabasePlatform {
|
||||
|
||||
dbDdlSyntax.setDropTableCascade("cascade");
|
||||
dbDdlSyntax.setDropIfExists("if exists");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and return the 'as of' predicate for a given table alias.
|
||||
* <p>
|
||||
* Each @History entity involved in the query has this predicate added using the related table alias.
|
||||
* </p>
|
||||
*/
|
||||
public String getAsOfPredicate(String asOfTableAlias, String asOfSysPeriod) {
|
||||
|
||||
// for Postgres we are using the 'timestamp with timezone range' data type
|
||||
// as our sys_period column so hence the predicate below
|
||||
StringBuilder sb = new StringBuilder(40);
|
||||
sb.append(asOfTableAlias).append(".").append(asOfSysPeriod).append(" @> ?::timestamptz");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Postgres specific sequence IdGenerator.
|
||||
*/
|
||||
@Override
|
||||
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds,
|
||||
String seqName, int batchSize) {
|
||||
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
|
||||
|
||||
return new PostgresSequenceIdGenerator(be, ds, seqName, batchSize);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
/**
|
||||
* Postgres support for history features.
|
||||
*/
|
||||
public class PostgresHistorySupport implements DbHistorySupport {
|
||||
|
||||
/**
|
||||
* Build and return the 'as of' predicate for a given table alias.
|
||||
* <p>
|
||||
* Each @History entity involved in the query has this predicate added using the related table alias.
|
||||
* </p>
|
||||
*/
|
||||
@Override
|
||||
public String getAsOfPredicate(String asOfTableAlias, String asOfSysPeriod) {
|
||||
|
||||
// for Postgres we are using the 'timestamp with timezone range' data type
|
||||
// as our sys_period column so hence the predicate below
|
||||
StringBuilder sb = new StringBuilder(40);
|
||||
sb.append(asOfTableAlias).append(".").append(asOfSysPeriod).append(" @> ?::timestamptz");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSysPeriodLower(String tableAlias, String sysPeriod) {
|
||||
return "lower("+tableAlias+"."+sysPeriod+")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSysPeriodUpper(String tableAlias, String sysPeriod) {
|
||||
return "upper("+tableAlias+"."+sysPeriod+")";
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ public class PostgresPlatform extends DatabasePlatform {
|
||||
this.clobDbType = Types.VARCHAR;
|
||||
|
||||
this.dbEncrypt = new PostgresDbEncrypt();
|
||||
this.historySupport = new PostgresHistorySupport();
|
||||
|
||||
// Use Identity and getGeneratedKeys
|
||||
this.dbIdentity.setIdType(IdType.IDENTITY);
|
||||
@@ -57,23 +58,8 @@ public class PostgresPlatform extends DatabasePlatform {
|
||||
|
||||
dbDdlSyntax.setDropTableCascade("cascade");
|
||||
dbDdlSyntax.setDropIfExists("if exists");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and return the 'as of' predicate for a given table alias.
|
||||
* <p>
|
||||
* Each @History entity involved in the query has this predicate added using the related table alias.
|
||||
* </p>
|
||||
*/
|
||||
public String getAsOfPredicate(String asOfTableAlias, String asOfSysPeriod) {
|
||||
|
||||
// for Postgres we are using the 'timestamp with timezone range' data type
|
||||
// as our sys_period column so hence the predicate below
|
||||
StringBuilder sb = new StringBuilder(40);
|
||||
sb.append(asOfTableAlias).append(".").append(asOfSysPeriod).append(" @> ?::timestamptz");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Postgres specific sequence IdGenerator.
|
||||
|
||||
@@ -1355,6 +1355,19 @@ public final class DefaultServer implements SpiEbeanServer {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<Version<T>> findVersions(Query<T> query, Transaction transaction) {
|
||||
|
||||
SpiOrmQueryRequest<T> request = createQueryRequest(Type.LIST, query, transaction);
|
||||
try {
|
||||
request.initTransIfRequired();
|
||||
return request.findVersions();
|
||||
|
||||
} finally {
|
||||
request.endTransIfRequired();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> List<T> findList(Query<T> query, Transaction t) {
|
||||
|
||||
|
||||
@@ -1,38 +1,42 @@
|
||||
package com.avaje.ebeaninternal.server.core;
|
||||
|
||||
import com.avaje.ebean.QueryIterator;
|
||||
import com.avaje.ebean.Version;
|
||||
import com.avaje.ebean.bean.BeanCollection;
|
||||
import com.avaje.ebeaninternal.api.BeanIdList;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The Object Relational query execution API.
|
||||
*/
|
||||
public interface OrmQueryEngine {
|
||||
|
||||
/**
|
||||
* Execute the 'find by id' query returning a single bean.
|
||||
*/
|
||||
public <T> T findId(OrmQueryRequest<T> request);
|
||||
/**
|
||||
* Execute the 'find by id' query returning a single bean.
|
||||
*/
|
||||
<T> T findId(OrmQueryRequest<T> request);
|
||||
|
||||
/**
|
||||
* Execute the findList, findSet, findMap query returning an appropriate BeanCollection.
|
||||
*/
|
||||
public <T> BeanCollection<T> findMany(OrmQueryRequest<T> request);
|
||||
/**
|
||||
* Execute the findList, findSet, findMap query returning an appropriate BeanCollection.
|
||||
*/
|
||||
<T> BeanCollection<T> findMany(OrmQueryRequest<T> request);
|
||||
|
||||
/**
|
||||
* Execute the query using a QueryIterator.
|
||||
*/
|
||||
public <T> QueryIterator<T> findIterate(OrmQueryRequest<T> request);
|
||||
|
||||
/**
|
||||
* Execute the row count query.
|
||||
*/
|
||||
public <T> int findRowCount(OrmQueryRequest<T> request);
|
||||
|
||||
/**
|
||||
* Execute the find id's query.
|
||||
*/
|
||||
public <T> BeanIdList findIds(OrmQueryRequest<T> request);
|
||||
<T> List<Version<T>> findVersions(OrmQueryRequest<T> request);
|
||||
|
||||
/**
|
||||
* Execute the query using a QueryIterator.
|
||||
*/
|
||||
<T> QueryIterator<T> findIterate(OrmQueryRequest<T> request);
|
||||
|
||||
/**
|
||||
* Execute the row count query.
|
||||
*/
|
||||
<T> int findRowCount(OrmQueryRequest<T> request);
|
||||
|
||||
/**
|
||||
* Execute the find id's query.
|
||||
*/
|
||||
<T> BeanIdList findIds(OrmQueryRequest<T> request);
|
||||
|
||||
}
|
||||
|
||||
@@ -271,6 +271,10 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
return (List<T>) queryEngine.findMany(this);
|
||||
}
|
||||
|
||||
public List<Version<T>> findVersions() {
|
||||
return queryEngine.findVersions(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query as findSet.
|
||||
*/
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.server.core;
|
||||
import com.avaje.ebean.QueryEachConsumer;
|
||||
import com.avaje.ebean.QueryEachWhileConsumer;
|
||||
import com.avaje.ebean.QueryIterator;
|
||||
import com.avaje.ebean.Version;
|
||||
import com.avaje.ebean.bean.BeanCollection;
|
||||
import com.avaje.ebeaninternal.api.SpiQuery;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
@@ -76,6 +77,11 @@ public interface SpiOrmQueryRequest<T> {
|
||||
*/
|
||||
QueryIterator<T> findIterate();
|
||||
|
||||
/**
|
||||
* Execute the finVersions() query.
|
||||
*/
|
||||
List<Version<T>> findVersions();
|
||||
|
||||
/**
|
||||
* Execute the query as findList.
|
||||
*/
|
||||
|
||||
@@ -114,4 +114,10 @@ public interface DbSqlContext {
|
||||
|
||||
String getRelativePrefix(String propName);
|
||||
|
||||
/**
|
||||
* Append the lower and upper bound columns into the select clause
|
||||
* for findVersions() queries.
|
||||
*/
|
||||
void appendHistorySysPeriod();
|
||||
|
||||
}
|
||||
|
||||
@@ -223,6 +223,11 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
|
||||
return exprList.asOf(asOf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Version<T>> findVersions() {
|
||||
return exprList.findVersions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query<T> apply(PathProperties pathProperties) {
|
||||
return exprList.apply(pathProperties);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.avaje.ebeaninternal.server.query;
|
||||
|
||||
import com.avaje.ebean.QueryIterator;
|
||||
import com.avaje.ebean.Version;
|
||||
import com.avaje.ebean.bean.*;
|
||||
import com.avaje.ebeaninternal.api.SpiQuery;
|
||||
import com.avaje.ebeaninternal.api.SpiQuery.Mode;
|
||||
@@ -22,6 +23,8 @@ import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -479,6 +482,30 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read version beans and their effective dates.
|
||||
*/
|
||||
public List<Version<T>> readVersions() throws SQLException {
|
||||
|
||||
List<Version<T>> versionList = new ArrayList<Version<T>>();
|
||||
|
||||
Version version;
|
||||
while ((version = readNextVersion()) != null) {
|
||||
versionList.add(version);
|
||||
}
|
||||
|
||||
updateExecutionStatistics();
|
||||
return versionList;
|
||||
}
|
||||
|
||||
private Version readNextVersion() throws SQLException {
|
||||
|
||||
if (moveToNextRow()) {
|
||||
return rootNode.loadVersion(this);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public BeanCollection<T> readCollection() throws SQLException {
|
||||
|
||||
while (hasNext()) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.avaje.ebean.RawSql.ColumnMapping;
|
||||
import com.avaje.ebean.RawSql.ColumnMapping.Column;
|
||||
import com.avaje.ebean.RawSqlBuilder;
|
||||
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
|
||||
import com.avaje.ebean.config.dbplatform.DbHistorySupport;
|
||||
import com.avaje.ebean.config.dbplatform.SqlLimitRequest;
|
||||
import com.avaje.ebean.config.dbplatform.SqlLimitResponse;
|
||||
import com.avaje.ebean.config.dbplatform.SqlLimiter;
|
||||
@@ -48,8 +49,10 @@ public class CQueryBuilder implements Constants {
|
||||
private final Map<String,String> asOfTableMapping;
|
||||
|
||||
private final String asOfSysPeriod;
|
||||
private final DbHistorySupport dbHistorySupport;
|
||||
private final CQueryHistorySupport historySupport;
|
||||
|
||||
private DatabasePlatform dbPlatform;
|
||||
private final DatabasePlatform dbPlatform;
|
||||
|
||||
/**
|
||||
* Create the SqlGenSelect.
|
||||
@@ -63,6 +66,8 @@ public class CQueryBuilder implements Constants {
|
||||
this.columnAliasPrefix = dbPlatform.getColumnAliasPrefix();
|
||||
this.sqlSelectBuilder = new RawSqlSelectClauseBuilder(dbPlatform, binder);
|
||||
|
||||
this.dbHistorySupport = dbPlatform.getHistorySupport();
|
||||
this.historySupport = new CQueryHistorySupport(dbHistorySupport, asOfTableMapping, asOfSysPeriod);
|
||||
this.sqlLimiter = dbPlatform.getSqlLimiter();
|
||||
this.rawSqlHandler = new CQueryBuilderRawSql(sqlLimiter, dbPlatform);
|
||||
|
||||
@@ -111,8 +116,7 @@ public class CQueryBuilder implements Constants {
|
||||
// use RawSql or generated Sql
|
||||
predicates.prepare(true);
|
||||
|
||||
Map<String,String> asOfMap = query.isAsOfQuery() ? asOfTableMapping : null;
|
||||
SqlTree sqlTree = createSqlTree(request, predicates, asOfMap);
|
||||
SqlTree sqlTree = createSqlTree(request, predicates, getHistorySupport(query));
|
||||
SqlLimitResponse s = buildSql(null, request, predicates, sqlTree);
|
||||
String sql = s.getSql();
|
||||
|
||||
@@ -123,6 +127,13 @@ public class CQueryBuilder implements Constants {
|
||||
return new CQueryFetchIds(request, predicates, sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the history support if this query needs it (is a 'as of' type query).
|
||||
*/
|
||||
private <T> CQueryHistorySupport getHistorySupport(SpiQuery<T> query) {
|
||||
return query.getTemporalMode() != SpiQuery.TemporalMode.CURRENT ? historySupport : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the row count query.
|
||||
*/
|
||||
@@ -164,8 +175,7 @@ public class CQueryBuilder implements Constants {
|
||||
|
||||
predicates.prepare(true);
|
||||
|
||||
Map<String,String> asOfMap = query.isAsOfQuery() ? asOfTableMapping : null;
|
||||
SqlTree sqlTree = createSqlTree(request, predicates, asOfMap);
|
||||
SqlTree sqlTree = createSqlTree(request, predicates, getHistorySupport(query));
|
||||
SqlLimitResponse s = buildSql(sqlSelect, request, predicates, sqlTree);
|
||||
String sql = s.getSql();
|
||||
if (hasMany || query.isRawSql()) {
|
||||
@@ -215,8 +225,7 @@ public class CQueryBuilder implements Constants {
|
||||
// Build the tree structure that represents the query.
|
||||
SpiQuery<T> query = request.getQuery();
|
||||
|
||||
Map<String,String> asOfMap = query.isAsOfQuery() ? asOfTableMapping : null;
|
||||
SqlTree sqlTree = createSqlTree(request, predicates, asOfMap);
|
||||
SqlTree sqlTree = createSqlTree(request, predicates, getHistorySupport(query));
|
||||
if (query.isAsOfQuery()) {
|
||||
sqlTree.addAsOfTableAlias(query);
|
||||
}
|
||||
@@ -249,13 +258,13 @@ public class CQueryBuilder implements Constants {
|
||||
* order by clauses that are not already included for the select clause.
|
||||
* </p>
|
||||
*/
|
||||
private SqlTree createSqlTree(OrmQueryRequest<?> request, CQueryPredicates predicates, Map<String,String> withHistoryTables) {
|
||||
private SqlTree createSqlTree(OrmQueryRequest<?> request, CQueryPredicates predicates, CQueryHistorySupport historySupport) {
|
||||
|
||||
if (request.isRawSql()) {
|
||||
return createRawSqlSqlTree(request, predicates);
|
||||
}
|
||||
|
||||
return new SqlTreeBuilder(tableAliasPlaceHolder, columnAliasPrefix, request, predicates, withHistoryTables).build();
|
||||
return new SqlTreeBuilder(tableAliasPlaceHolder, columnAliasPrefix, request, predicates, historySupport).build();
|
||||
}
|
||||
|
||||
private SqlTree createRawSqlSqlTree(OrmQueryRequest<?> request, CQueryPredicates predicates) {
|
||||
@@ -418,7 +427,7 @@ public class CQueryBuilder implements Constants {
|
||||
if (i > 0) {
|
||||
sb.append(" and ");
|
||||
}
|
||||
sb.append(dbPlatform.getAsOfPredicate(asOfTableAlias.get(i), asOfSysPeriod));
|
||||
sb.append(dbHistorySupport.getAsOfPredicate(asOfTableAlias.get(i), asOfSysPeriod));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package com.avaje.ebeaninternal.server.query;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.avaje.ebean.Version;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -154,6 +156,35 @@ public class CQueryEngine {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the find versions query returning version beans.
|
||||
*/
|
||||
public <T> List<Version<T>> findVersions(OrmQueryRequest<T> request) {
|
||||
|
||||
CQuery<T> cquery = queryBuilder.buildQuery(request);
|
||||
try {
|
||||
cquery.prepareBindExecuteQuery();
|
||||
if (request.isLogSql()) {
|
||||
logSql(cquery);
|
||||
}
|
||||
|
||||
List<Version<T>> versions = cquery.readVersions();
|
||||
if (request.isLogSummary()) {
|
||||
logFindManySummary(cquery);
|
||||
}
|
||||
|
||||
return versions;
|
||||
|
||||
} catch (SQLException e) {
|
||||
throw cquery.createPersistenceException(e);
|
||||
|
||||
} finally {
|
||||
if (cquery != null) {
|
||||
cquery.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a list/map/set of beans.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.avaje.ebeaninternal.server.query;
|
||||
|
||||
import com.avaje.ebean.config.dbplatform.DbHistorySupport;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Helper to support history functions.
|
||||
*/
|
||||
public class CQueryHistorySupport {
|
||||
|
||||
/**
|
||||
* The DB specific support.
|
||||
*/
|
||||
private final DbHistorySupport dbHistorySupport;
|
||||
|
||||
/**
|
||||
* The mapping of base tables to their matching 'with history' views.
|
||||
*/
|
||||
private final Map<String,String> asOfTableMap;
|
||||
|
||||
/**
|
||||
* The sys period column.
|
||||
*/
|
||||
private final String asOfSysPeriod;
|
||||
|
||||
public CQueryHistorySupport(DbHistorySupport dbHistorySupport, Map<String, String> asOfTableMap, String asOfSysPeriod) {
|
||||
this.dbHistorySupport = dbHistorySupport;
|
||||
this.asOfTableMap = asOfTableMap;
|
||||
this.asOfSysPeriod = asOfSysPeriod;
|
||||
}
|
||||
|
||||
public String getAsOfView(String table) {
|
||||
return asOfTableMap.get(table);
|
||||
}
|
||||
|
||||
public String getSysPeriodLower(String tableAlias) {
|
||||
|
||||
return dbHistorySupport.getSysPeriodLower(tableAlias, asOfSysPeriod);
|
||||
}
|
||||
|
||||
public String getSysPeriodUpper(String tableAlias) {
|
||||
|
||||
return dbHistorySupport.getSysPeriodUpper(tableAlias, asOfSysPeriod);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import com.avaje.ebeaninternal.server.util.ArrayStack;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
||||
public class DefaultDbSqlContext implements DbSqlContext {
|
||||
|
||||
@@ -46,22 +45,22 @@ public class DefaultDbSqlContext implements DbSqlContext {
|
||||
|
||||
private ArrayList<BeanProperty> encryptedProps;
|
||||
|
||||
private final Map<String,String> asOfTableMap;
|
||||
private final CQueryHistorySupport historySupport;
|
||||
|
||||
private final boolean asOfQuery;
|
||||
private final boolean historyQuery;
|
||||
|
||||
/**
|
||||
* Construct for SELECT clause (with column alias settings).
|
||||
*/
|
||||
public DefaultDbSqlContext(SqlTreeAlias alias, String tableAliasPlaceHolder,
|
||||
String columnAliasPrefix, boolean alwaysUseColumnAlias, Map<String,String> asOfTableMap) {
|
||||
String columnAliasPrefix, boolean alwaysUseColumnAlias, CQueryHistorySupport historySupport) {
|
||||
|
||||
this.alias = alias;
|
||||
this.tableAliasPlaceHolder = tableAliasPlaceHolder;
|
||||
this.columnAliasPrefix = columnAliasPrefix;
|
||||
this.useColumnAlias = alwaysUseColumnAlias;
|
||||
this.asOfTableMap = asOfTableMap;
|
||||
this.asOfQuery = (asOfTableMap != null);
|
||||
this.historySupport = historySupport;
|
||||
this.historyQuery = (historySupport != null);
|
||||
}
|
||||
|
||||
public void addEncryptedProp(BeanProperty p) {
|
||||
@@ -106,13 +105,14 @@ public class DefaultDbSqlContext implements DbSqlContext {
|
||||
|
||||
sb.append(" ");
|
||||
sb.append(type);
|
||||
if (!asOfQuery) {
|
||||
if (!historyQuery) {
|
||||
sb.append(" ").append(table).append(" ");
|
||||
|
||||
} else {
|
||||
// check if there is an associated history table and if so
|
||||
// use the unionAll view - we expect an additional predicate to match
|
||||
String withHistoryTable = asOfTableMap.get(table);
|
||||
String withHistoryTable = historySupport.getAsOfView(table);
|
||||
|
||||
if (withHistoryTable != null) {
|
||||
// there is an associated history table and view so use that
|
||||
sb.append(" ").append(withHistoryTable).append(" ");
|
||||
@@ -227,6 +227,29 @@ public class DefaultDbSqlContext implements DbSqlContext {
|
||||
sb.append(converted);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendHistorySysPeriod() {
|
||||
|
||||
String tableAlias = tableAliasStack.peek();
|
||||
|
||||
sb.append(COMMA);
|
||||
sb.append(historySupport.getSysPeriodLower(tableAlias));
|
||||
appendColumnAlias();
|
||||
|
||||
sb.append(COMMA);
|
||||
sb.append(historySupport.getSysPeriodUpper(tableAlias));
|
||||
appendColumnAlias();
|
||||
}
|
||||
|
||||
private void appendColumnAlias() {
|
||||
if (useColumnAlias) {
|
||||
sb.append(" ");
|
||||
sb.append(columnAliasPrefix);
|
||||
sb.append(columnIndex);
|
||||
}
|
||||
columnIndex++;
|
||||
}
|
||||
|
||||
public void appendColumn(String column) {
|
||||
appendColumn(tableAliasStack.peek(), column);
|
||||
}
|
||||
@@ -244,12 +267,7 @@ public class DefaultDbSqlContext implements DbSqlContext {
|
||||
sb.append(PERIOD);
|
||||
sb.append(column);
|
||||
}
|
||||
if (useColumnAlias) {
|
||||
sb.append(" ");
|
||||
sb.append(columnAliasPrefix);
|
||||
sb.append(columnIndex);
|
||||
}
|
||||
columnIndex++;
|
||||
appendColumnAlias();
|
||||
}
|
||||
|
||||
public String peekTableAlias() {
|
||||
@@ -260,12 +278,7 @@ public class DefaultDbSqlContext implements DbSqlContext {
|
||||
sb.append(COMMA);
|
||||
sb.append(rawcolumnWithTableAlias);
|
||||
|
||||
if (useColumnAlias) {
|
||||
sb.append(" ");
|
||||
sb.append(columnAliasPrefix);
|
||||
sb.append(columnIndex);
|
||||
}
|
||||
columnIndex++;
|
||||
appendColumnAlias();
|
||||
}
|
||||
|
||||
public int length() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.avaje.ebeaninternal.server.query;
|
||||
|
||||
import com.avaje.ebean.QueryIterator;
|
||||
import com.avaje.ebean.Version;
|
||||
import com.avaje.ebean.bean.BeanCollection;
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebean.event.BeanFindController;
|
||||
@@ -12,6 +13,7 @@ import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Main Finder implementation.
|
||||
@@ -65,6 +67,13 @@ public class DefaultOrmQueryEngine implements OrmQueryEngine {
|
||||
return queryEngine.findIterate(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<Version<T>> findVersions(OrmQueryRequest<T> request) {
|
||||
|
||||
flushJdbcBatchOnQuery(request);
|
||||
return queryEngine.findVersions(request);
|
||||
}
|
||||
|
||||
public <T> BeanCollection<T> findMany(OrmQueryRequest<T> request) {
|
||||
|
||||
flushJdbcBatchOnQuery(request);
|
||||
|
||||
@@ -98,7 +98,7 @@ public class SqlTreeBuilder {
|
||||
* to the root node.
|
||||
*/
|
||||
public SqlTreeBuilder(String tableAliasPlaceHolder, String columnAliasPrefix,
|
||||
OrmQueryRequest<?> request, CQueryPredicates predicates, Map<String,String> asOfTables) {
|
||||
OrmQueryRequest<?> request, CQueryPredicates predicates, CQueryHistorySupport historySupport) {
|
||||
|
||||
this.rawSql = false;
|
||||
this.rawNoId = false;
|
||||
@@ -112,7 +112,7 @@ public class SqlTreeBuilder {
|
||||
|
||||
this.predicates = predicates;
|
||||
this.alias = new SqlTreeAlias(request.getQuery().getAlias()==null?request.getBeanDescriptor().getBaseTableAlias():request.getQuery().getAlias());
|
||||
this.ctx = new DefaultDbSqlContext(alias, tableAliasPlaceHolder, columnAliasPrefix, !subQuery, asOfTables);
|
||||
this.ctx = new DefaultDbSqlContext(alias, tableAliasPlaceHolder, columnAliasPrefix, !subQuery, historySupport);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.avaje.ebeaninternal.server.query;
|
||||
|
||||
import com.avaje.ebean.Version;
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebeaninternal.api.SpiQuery;
|
||||
import com.avaje.ebeaninternal.server.deploy.DbReadContext;
|
||||
@@ -47,4 +48,8 @@ public interface SqlTreeNode {
|
||||
*/
|
||||
EntityBean load(DbReadContext ctx, EntityBean localBean, EntityBean contextBean) throws SQLException;
|
||||
|
||||
/**
|
||||
* Load a version of a @History bean with effective dates.
|
||||
*/
|
||||
<T> Version<T> loadVersion(DbReadContext ctx) throws SQLException;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package com.avaje.ebeaninternal.server.query;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.avaje.ebean.Version;
|
||||
import com.avaje.ebean.bean.BeanCollection;
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebean.bean.EntityBeanIntercept;
|
||||
@@ -73,6 +75,8 @@ public class SqlTreeNodeBean implements SqlTreeNode {
|
||||
|
||||
protected final SpiQuery.TemporalMode temporalMode;
|
||||
|
||||
protected final boolean temporalVersions;
|
||||
|
||||
private final IdBinder lazyLoadParentIdBinder;
|
||||
|
||||
protected String baseTableAlias;
|
||||
@@ -101,6 +105,8 @@ public class SqlTreeNodeBean implements SqlTreeNode {
|
||||
this.nodeBeanProp = beanProp;
|
||||
this.desc = desc;
|
||||
this.temporalMode = temporalMode;
|
||||
this.temporalVersions = temporalMode == SpiQuery.TemporalMode.VERSIONS;
|
||||
|
||||
this.inheritInfo = desc.getInheritInfo();
|
||||
this.extraWhere = (beanProp == null) ? null : beanProp.getExtraWhere();
|
||||
|
||||
@@ -108,7 +114,7 @@ public class SqlTreeNodeBean implements SqlTreeNode {
|
||||
|
||||
// the bean has an Id property and we want to use it
|
||||
this.readId = withId && (desc.getIdProperty() != null);
|
||||
this.disableLazyLoad = !readId || desc.isSqlSelectBased();
|
||||
this.disableLazyLoad = !readId || desc.isSqlSelectBased() || temporalVersions;
|
||||
|
||||
this.tableJoins = props.getTableJoins();
|
||||
|
||||
@@ -164,6 +170,20 @@ public class SqlTreeNodeBean implements SqlTreeNode {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the version bean.
|
||||
*/
|
||||
public <T> Version<T> loadVersion(DbReadContext ctx) throws SQLException {
|
||||
|
||||
// read the sys period lower and upper bounds
|
||||
// these are always the first 2 columns in the resultSet
|
||||
Timestamp start = ctx.getDataReader().getTimestamp();
|
||||
Timestamp end = ctx.getDataReader().getTimestamp();
|
||||
T bean = (T)load(ctx, null, null);
|
||||
|
||||
return new Version(bean, start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* read the properties from the resultSet.
|
||||
*/
|
||||
@@ -206,14 +226,14 @@ public class SqlTreeNodeBean implements SqlTreeNode {
|
||||
|
||||
Mode queryMode = ctx.getQueryMode();
|
||||
|
||||
PersistenceContext persistenceContext = !readId ? null : ctx.getPersistenceContext();
|
||||
PersistenceContext persistenceContext = (!readId || temporalVersions) ? null : ctx.getPersistenceContext();
|
||||
|
||||
if (readId) {
|
||||
Object id = localIdBinder.readSet(ctx, localBean);
|
||||
if (id == null) {
|
||||
// bean must be null...
|
||||
localBean = null;
|
||||
} else {
|
||||
} else if (!temporalVersions) {
|
||||
// check the PersistenceContext to see if the bean already exists
|
||||
contextBean = (EntityBean)persistenceContext.putIfAbsent(id, localBean);
|
||||
if (contextBean == null) {
|
||||
@@ -278,7 +298,7 @@ public class SqlTreeNodeBean implements SqlTreeNode {
|
||||
|
||||
if (!lazyLoadMany && localBean != null) {
|
||||
ctx.setCurrentPrefix(prefix, pathMap);
|
||||
if (readId) {
|
||||
if (readId && !temporalVersions) {
|
||||
createListProxies(localDesc, ctx, localBean);
|
||||
}
|
||||
localDesc.postLoad(localBean, null);
|
||||
@@ -293,7 +313,11 @@ public class SqlTreeNodeBean implements SqlTreeNode {
|
||||
ebi.setLoaded();
|
||||
}
|
||||
|
||||
if (partialObject) {
|
||||
if (disableLazyLoad) {
|
||||
// bean does not have an Id or is SqlSelect based
|
||||
ebi.setDisableLazyLoad(true);
|
||||
|
||||
} else if (partialObject) {
|
||||
if (readId) {
|
||||
// register for lazy loading
|
||||
ctx.register(null, ebi);
|
||||
@@ -302,11 +326,7 @@ public class SqlTreeNodeBean implements SqlTreeNode {
|
||||
ebi.setFullyLoadedBean(true);
|
||||
}
|
||||
|
||||
if (disableLazyLoad) {
|
||||
// bean does not have an Id or is SqlSelect based
|
||||
ebi.setDisableLazyLoad(true);
|
||||
}
|
||||
if (ctx.isAutoFetchProfiling()) {
|
||||
if (ctx.isAutoFetchProfiling() && !disableLazyLoad) {
|
||||
// collect autofetch profiling for this bean...
|
||||
ctx.profileBean(ebi, prefix);
|
||||
}
|
||||
@@ -317,7 +337,7 @@ public class SqlTreeNodeBean implements SqlTreeNode {
|
||||
nodeBeanProp.setValue(parentBean, contextBean);
|
||||
}
|
||||
|
||||
if (!readId) {
|
||||
if (!readId || temporalVersions) {
|
||||
// a bean with no Id (never found in context)
|
||||
return localBean;
|
||||
|
||||
@@ -358,7 +378,12 @@ public class SqlTreeNodeBean implements SqlTreeNode {
|
||||
|
||||
ctx.pushJoin(prefix);
|
||||
ctx.pushTableAlias(prefix);
|
||||
|
||||
|
||||
if (temporalVersions) {
|
||||
// select sys_period lower and upper columns
|
||||
ctx.appendHistorySysPeriod();
|
||||
}
|
||||
|
||||
if (lazyLoadParent != null) {
|
||||
lazyLoadParent.addSelectExported(ctx, prefix);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.avaje.ebeaninternal.server.query;
|
||||
|
||||
import com.avaje.ebean.Version;
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebeaninternal.api.SpiQuery;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc;
|
||||
@@ -126,4 +127,11 @@ public class SqlTreeNodeExtraJoin implements SqlTreeNode {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does nothing.
|
||||
*/
|
||||
@Override
|
||||
public <T> Version<T> loadVersion(DbReadContext ctx) throws SQLException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.avaje.ebeaninternal.server.query;
|
||||
|
||||
import com.avaje.ebean.Version;
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebeaninternal.api.SpiQuery;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc;
|
||||
@@ -98,4 +99,9 @@ public class SqlTreeNodeManyWhereJoin implements SqlTreeNode {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Version<T> loadVersion(DbReadContext ctx) throws SQLException {
|
||||
// nothing to do here
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,6 +163,8 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
*/
|
||||
private Timestamp asOf;
|
||||
|
||||
private TemporalMode temporalMode = TemporalMode.CURRENT;
|
||||
|
||||
private int bufferFetchSizeHint;
|
||||
|
||||
private boolean usageProfiling = true;
|
||||
@@ -288,6 +290,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
}
|
||||
|
||||
public DefaultOrmQuery<T> asOf(Timestamp asOfDateTime) {
|
||||
this.temporalMode = (asOfDateTime != null) ? TemporalMode.AS_OF : TemporalMode.CURRENT;
|
||||
this.asOf = asOfDateTime;
|
||||
return this;
|
||||
}
|
||||
@@ -617,7 +620,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
|
||||
@Override
|
||||
public TemporalMode getTemporalMode() {
|
||||
return asOf == null ? TemporalMode.CURRENT : TemporalMode.AS_OF;
|
||||
return temporalMode;
|
||||
}
|
||||
|
||||
public boolean isAsOfQuery() {
|
||||
@@ -952,6 +955,12 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
server.findEach(this, consumer, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Version<T>> findVersions() {
|
||||
this.temporalMode = TemporalMode.VERSIONS;
|
||||
return server.findVersions(this, null);
|
||||
}
|
||||
|
||||
public QueryIterator<T> findIterate() {
|
||||
return server.findIterate(this, null);
|
||||
}
|
||||
|
||||
@@ -113,6 +113,11 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
|
||||
return query.asOf(asOf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Version<T>> findVersions() {
|
||||
return query.findVersions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> where() {
|
||||
return query.where();
|
||||
|
||||
Reference in New Issue
Block a user