#350 - ENH: Add @History findVersions() query ... returning historic versions for a given bean using history view

This commit is contained in:
Robin Bygrave
2015-07-24 16:20:15 +12:00
parent 61a0651439
commit 1ec2f74df9
29 changed files with 500 additions and 111 deletions
@@ -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.
*/
+9
View File
@@ -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();