#347 - @History support with query.asOf(Timestamp)

This commit is contained in:
Robin Bygrave
2015-07-24 10:18:17 +12:00
parent 3cafb37a73
commit 82489636d5
37 changed files with 601 additions and 92 deletions
@@ -4,6 +4,7 @@ import com.avaje.ebean.text.PathProperties;
import org.jetbrains.annotations.Nullable;
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -95,6 +96,17 @@ public interface ExpressionList<T> extends Serializable {
*/
Query<T> apply(PathProperties pathProperties);
/**
* Perform an 'As of' query using history tables to return the object graph
* as of a time in the past.
* <p>
* To perform this query the DB must have underlying history tables.
* </p>
*
* @param asOf the date time in the past at which you want to view the data
*/
Query<T> asOf(Timestamp asOf);
/**
* Execute the query iterating over the results.
*
+12
View File
@@ -4,6 +4,7 @@ import com.avaje.ebean.text.PathProperties;
import org.jetbrains.annotations.Nullable;
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -285,6 +286,17 @@ public interface Query<T> extends Serializable {
*/
Query<T> setRawSql(RawSql rawSql);
/**
* Perform an 'As of' query using history tables to return the object graph
* as of a time in the past.
* <p>
* To perform this query the DB must have underlying history tables.
* </p>
*
* @param asOf the date time in the past at which you want to view the data
*/
Query<T> asOf(Timestamp asOf);
/**
* Cancel the query execution if supported by the underlying database and
* driver.
@@ -0,0 +1,19 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Marks an entity bean as having history support.
* <p>
* In Postgres for example this means there is an associated history table which is
* typically automatically populated via database triggers.
* </p>
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface History {
}
@@ -0,0 +1,25 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Marks a property as being excluded from history.
* <p>
* This means the property values are not maintained in the history table. Typically this
* would be placed on relatively large properties (Clobs, Blobs, large varchar columns etc)
* that are considered not interesting enough to maintain history on excluding them reduces
* underlying database costs.
* </p>
* <p>
* When placed on a ManyToMany this means that the intersection table does not have history
* support.
* </p>
*/
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface HistoryExclude {
}
@@ -144,6 +144,18 @@ public class ServerConfig {
*/
private int databaseSequenceBatchSize = 20;
/**
* 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.
*/
private String asOfViewSuffix = "_with_history";
/**
* Column used to support history and 'As of' queries. This column is a timestamp range
* or equivalent.
*/
private String asOfSysPeriod = "sys_period";
/**
* Use for transaction scoped batch mode.
*/
@@ -579,6 +591,38 @@ public class ServerConfig {
this.databaseSequenceBatchSize = databaseSequenceBatchSize;
}
/**
* Return the 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.
*/
public String getAsOfViewSuffix() {
return asOfViewSuffix;
}
/**
* Set the 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.
*/
public void setAsOfViewSuffix(String asOfViewSuffix) {
this.asOfViewSuffix = asOfViewSuffix;
}
/**
* Return the database column used to support history and 'As of' queries. This column is a timestamp range
* or equivalent.
*/
public String getAsOfSysPeriod() {
return asOfSysPeriod;
}
/**
* Set the database column used to support history and 'As of' queries. This column is a timestamp range
* or equivalent.
*/
public void setAsOfSysPeriod(String asOfSysPeriod) {
this.asOfSysPeriod = asOfSysPeriod;
}
/**
* Return true if we are running in a JTA Transaction manager.
*/
@@ -1809,6 +1853,8 @@ public class ServerConfig {
persistenceContextScope = PersistenceContextScope.valueOf(p.get("persistenceContextScope", "TRANSACTION"));
asOfViewSuffix = p.get("asOfViewSuffix", asOfViewSuffix);
asOfSysPeriod = p.get("asOfSysPeriod", asOfSysPeriod);
dataSourceJndiName = p.get("dataSourceJndiName", dataSourceJndiName);
databaseSequenceBatchSize = p.getInt("databaseSequenceBatchSize", databaseSequenceBatchSize);
databaseBooleanTrue = p.get("databaseBooleanTrue", databaseBooleanTrue);
@@ -442,6 +442,10 @@ public class DatabasePlatform {
return disallowBatchOnCascade;
}
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.
*/
@@ -60,12 +60,24 @@ public class PostgresPlatform extends DatabasePlatform {
}
/**
* 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) {
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);
}
@@ -19,6 +19,7 @@ import com.avaje.ebeaninternal.server.querydefn.NaturalKeyBindParam;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties;
import java.sql.Timestamp;
import java.util.List;
/**
@@ -86,6 +87,30 @@ public interface SpiQuery<T> extends Query<T> {
SUBQUERY
}
enum TemporalMode {
/**
* Query runs against current data (normal).
*/
CURRENT,
/**
* Query runs potentially returning many versions of the same bean.
*/
VERSIONS,
/**
* Query runs 'As Of' a given date time.
*/
AS_OF;
/**
* Return the mode of the query of if null return CURRENT mode.
*/
public static TemporalMode of(SpiQuery<?> query) {
return (query != null) ? query.getTemporalMode() : TemporalMode.CURRENT;
}
}
/**
* Return the PersistenceContextScope that this query should use.
* <p>
@@ -115,6 +140,31 @@ public interface SpiQuery<T> extends Query<T> {
*/
Mode getMode();
/**
* Return the Temporal mode for the query.
*/
TemporalMode getTemporalMode();
/**
* Return true if this is a 'As Of' query.
*/
boolean isAsOfQuery();
/**
* Return the asOf Timestamp which the query should run as.
*/
Timestamp getAsOf();
/**
* Add a table alias for a @History entity involved in a 'As Of' query.
*/
void addAsOfTableAlias(String tableAlias);
/**
* Return the list of table alias involved in a 'As Of' query that have @History support.
*/
List<String> getAsOfTableAlias();
/**
* Return a listener that wants to be notified when the bean collection is
* first used.
@@ -41,6 +41,8 @@ import com.avaje.ebeaninternal.server.type.DefaultTypeManager;
import com.avaje.ebeaninternal.server.type.TypeManager;
import com.fasterxml.jackson.core.JsonFactory;
import java.util.Map;
/**
* Used to extend the ServerConfig with additional objects used to configure and
* construct an EbeanServer.
@@ -116,11 +118,11 @@ public class InternalConfiguration {
this.deployUtil = new DeployUtil(typeManager, serverConfig);
this.beanDescriptorManager = new BeanDescriptorManager(this);
beanDescriptorManager.deploy();
Map<String, String> asOfTableMapping = beanDescriptorManager.deploy();
this.transactionManager = createTransactionManager();
this.cQueryEngine = new CQueryEngine(serverConfig.getDatabasePlatform(), binder);
this.cQueryEngine = new CQueryEngine(serverConfig.getDatabasePlatform(), binder, asOfTableMapping, serverConfig.getAsOfSysPeriod());
ExternalTransactionManager externalTransactionManager = serverConfig.getExternalTransactionManager();
if (externalTransactionManager == null && serverConfig.isUseJtaTransactionManager()) {
@@ -112,6 +112,8 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
* The base database table.
*/
private final String baseTable;
private final String baseTableAsOf;
private final boolean historySupport;
/**
* Map of BeanProperty Linked so as to preserve order.
@@ -332,8 +334,9 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
this.updateChangesOnly = deploy.isUpdateChangesOnly();
this.compoundUniqueConstraints = deploy.getCompoundUniqueConstraints();
this.historySupport = deploy.isHistorySupport();
this.baseTable = InternString.intern(deploy.getBaseTable());
this.baseTableAsOf = deploy.getBaseTableAsOf();
this.autoFetchTunable = EntityType.ORM.equals(entityType) && (beanFinder == null);
// helper object used to derive lists of properties
@@ -496,13 +499,19 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
* These properties need to be initialised prior to the association properties
* as they are used to get the imported and exported properties.
* </p>
* @param withHistoryTables map populated if @History is supported on this entity bean
*/
public void initialiseId() {
public void initialiseId(Map<String, String> withHistoryTables) {
if (logger.isTraceEnabled()) {
logger.trace("BeanDescriptor initialise " + fullName);
}
if (historySupport) {
// add mapping (used to swap out baseTable for asOf queries)
withHistoryTables.put(baseTable, baseTableAsOf);
}
if (inheritInfo != null) {
inheritInfo.setDescriptor(this);
}
@@ -522,8 +531,25 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
/**
* Initialise the exported and imported parts for associated properties.
*
* @param asOfTableMap the map of base tables to associated 'with history' tables
* @param asOfViewSuffix the suffix added to the table name to derive the 'with history' view name
*/
public void initialiseOther() {
public void initialiseOther(Map<String, String> asOfTableMap, String asOfViewSuffix) {
if (historySupport) {
// history support on this bean so check all associated intersection tables
// and if they are not excluded register the associated 'with history' table
for (int i = 0; i < propertiesManyToMany.length; i++) {
if (!propertiesManyToMany[i].isExcludedFromHistory()) {
// this intersection table has history support so also register
// it into the asOfTableMap
TableJoin intersectionTableJoin = propertiesManyToMany[i].getIntersectionTableJoin();
String intersectionTableName = intersectionTableJoin.getTable();
asOfTableMap.put(intersectionTableName, intersectionTableName + asOfViewSuffix);
}
}
}
if (!isEmbedded()) {
// initialise all the non-id properties
@@ -761,7 +787,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
}
public void cacheBeanPut(T bean) {
cacheBeanPutData((EntityBean)bean);
cacheBeanPutData((EntityBean) bean);
}
/**
@@ -1628,6 +1654,24 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
return baseTable;
}
/**
* Return the base table to use given the query temporal mode.
*/
public String getBaseTable(SpiQuery.TemporalMode mode) {
switch (mode) {
case VERSIONS: return baseTableAsOf;
case AS_OF: return baseTableAsOf;
default: return baseTable;
}
}
/**
* Return true if this entity bean has history support.
*/
public boolean isHistorySupport() {
return historySupport;
}
/**
* Return the identity generation type.
*/
@@ -10,6 +10,7 @@ import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.config.EncryptKey;
import com.avaje.ebean.config.EncryptKeyManager;
import com.avaje.ebean.config.NamingConvention;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebean.config.dbplatform.DbIdentity;
import com.avaje.ebean.config.dbplatform.IdGenerator;
@@ -51,7 +52,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
private static final BeanDescComparator beanDescComparator = new BeanDescComparator();
private final ReadAnnotations readAnnotations = new ReadAnnotations();
private final ReadAnnotations readAnnotations;
private final TransientProperties transientProperties;
@@ -127,34 +128,45 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
private final boolean eagerFetchLobs;
private final String asOfViewSuffix;
/**
* Map of base tables to 'with history views' used to support 'as of' queries.
*/
private final Map<String,String> asOfTableMap = new HashMap<String, String>();
/**
* Create for a given database dbConfig.
*/
public BeanDescriptorManager(InternalConfiguration config) {
this.serverName = InternString.intern(config.getServerConfig().getName());
ServerConfig serverConfig = config.getServerConfig();
this.serverName = InternString.intern(serverConfig.getName());
this.cacheManager = config.getCacheManager();
this.xmlConfig = config.getXmlConfig();
this.dbSequenceBatchSize = config.getServerConfig().getDatabaseSequenceBatchSize();
this.dbSequenceBatchSize = serverConfig.getDatabaseSequenceBatchSize();
this.backgroundExecutor = config.getBackgroundExecutor();
this.dataSource = config.getServerConfig().getDataSource();
this.encryptKeyManager = config.getServerConfig().getEncryptKeyManager();
this.databasePlatform = config.getServerConfig().getDatabasePlatform();
this.dataSource = serverConfig.getDataSource();
this.encryptKeyManager = serverConfig.getEncryptKeyManager();
this.databasePlatform = serverConfig.getDatabasePlatform();
this.idBinderFactory = new IdBinderFactory(databasePlatform.isIdInExpandedForm());
this.eagerFetchLobs = config.getServerConfig().isEagerFetchLobs();
this.eagerFetchLobs = serverConfig.isEagerFetchLobs();
this.asOfViewSuffix = serverConfig.getAsOfViewSuffix();
this.readAnnotations = new ReadAnnotations(asOfViewSuffix);
this.bootupClasses = config.getBootupClasses();
this.createProperties = config.getDeployCreateProperties();
this.typeManager = config.getTypeManager();
this.namingConvention = config.getServerConfig().getNamingConvention();
this.namingConvention = serverConfig.getNamingConvention();
this.dbIdentity = config.getDatabasePlatform().getDbIdentity();
this.deplyInherit = config.getDeployInherit();
this.deployOrmXml = config.getDeployOrmXml();
this.deployUtil = config.getDeployUtil();
this.beanManagerFactory = new BeanManagerFactory(config.getServerConfig(), config.getDatabasePlatform());
this.beanManagerFactory = new BeanManagerFactory(serverConfig, config.getDatabasePlatform());
this.updateChangesOnly = config.getServerConfig().isUpdateChangesOnly();
this.updateChangesOnly = serverConfig.isUpdateChangesOnly();
this.beanLifecycleAdapterFactory = new BeanLifecycleAdapterFactory();
this.persistControllerManager = new PersistControllerManager(bootupClasses);
@@ -205,7 +217,10 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
return idBinderFactory.createIdBinder(idProperty);
}
public void deploy() {
/**
* Deploy returning the asOfTableMap (which is required by the SQL builders).
*/
public Map<String,String> deploy() {
try {
createListeners();
@@ -237,6 +252,9 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
deplyInfoMap.clear();
deplyInfoMap = null;
return asOfTableMap;
} catch (RuntimeException e) {
String msg = "Error in deployment";
logger.error(msg, e);
@@ -324,7 +342,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
// first (as they are needed to initialise the
// associated properties in the second pass).
for (BeanDescriptor<?> d : descMap.values()) {
d.initialiseId();
d.initialiseId(asOfTableMap);
}
// PASS 2:
@@ -336,7 +354,10 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
// PASS 3:
// now initialise all the associated properties
for (BeanDescriptor<?> d : descMap.values()) {
d.initialiseOther();
// also look for intersection tables with
// associated history support and register them
// into the asOfTableMap
d.initialiseOther(asOfTableMap, asOfViewSuffix);
}
// create BeanManager for each non-embedded entity bean
@@ -973,7 +994,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
if (!EntityType.ORM.equals(desc.getEntityType())) {
// not using base table
desc.setBaseTable(null);
desc.setBaseTable(null, null);
}
// mark transient properties
@@ -173,6 +173,8 @@ public class BeanProperty implements ElPropertyValue {
*/
final int dbType;
final boolean excludedFromHistory;
/**
* The default value to insert if null.
*/
@@ -256,6 +258,7 @@ public class BeanProperty implements ElPropertyValue {
this.dbRead = deploy.isDbRead();
this.dbInsertable = deploy.isDbInsertable();
this.dbUpdatable = deploy.isDbUpdateable();
this.excludedFromHistory = deploy.isExcludedFromHistory();
this.secondaryTable = deploy.isSecondaryTable();
if (secondaryTable) {
@@ -341,6 +344,7 @@ public class BeanProperty implements ElPropertyValue {
this.sqlFormulaSelect = InternString.intern(override.getSqlFormulaSelect());
this.formula = sqlFormulaSelect != null;
this.excludedFromHistory = source.excludedFromHistory;
this.fetchEager = source.fetchEager;
this.unidirectionalShadow = source.unidirectionalShadow;
this.discriminator = source.discriminator;
@@ -1004,6 +1008,13 @@ public class BeanProperty implements ElPropertyValue {
return dbEncryptedType;
}
/**
* Return true if this property is excluded from history.
*/
public boolean isExcludedFromHistory() {
return excludedFromHistory;
}
/**
* Return true if this property should be included in an Insert.
*/
@@ -10,108 +10,108 @@ public interface DbSqlContext {
/**
* Add a join to the sql query.
*/
public void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2, String inheritance);
void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2, String inheritance);
public void pushSecondaryTableAlias(String alias);
void pushSecondaryTableAlias(String alias);
/**
* Push the current table alias onto the stack.
*/
public void pushTableAlias(String tableAlias);
void pushTableAlias(String tableAlias);
/**
* Pop the current table alias from the stack.
*/
public void popTableAlias();
void popTableAlias();
/**
* Add an encrypted property which will require additional binding.
*/
public void addEncryptedProp(BeanProperty prop);
void addEncryptedProp(BeanProperty prop);
/**
* Return a list of encrypted properties which require additional binding.
*/
public BeanProperty[] getEncryptedProps();
BeanProperty[] getEncryptedProps();
/**
* Append a char directly to the SQL buffer.
*/
public DbSqlContext append(char s);
DbSqlContext append(char s);
/**
* Append a string directly to the SQL buffer.
*/
public DbSqlContext append(String s);
DbSqlContext append(String s);
/**
* Peek the current table alias.
*/
public String peekTableAlias();
String peekTableAlias();
/**
* Add a raw column to the sql.
*/
public void appendRawColumn(String rawcolumnWithTableAlias);
void appendRawColumn(String rawcolumnWithTableAlias);
/**
* Append a column with an explicit table alias.
*/
public void appendColumn(String tableAlias, String column);
void appendColumn(String tableAlias, String column);
/**
* Append a column with the current table alias.
*/
public void appendColumn(String column);
void appendColumn(String column);
/**
* Append a Sql Formula select. This converts the "${ta}" keyword to the
* current table alias.
*/
public void appendFormulaSelect(String sqlFormulaSelect);
void appendFormulaSelect(String sqlFormulaSelect);
/**
* Append a Sql Formula join. This converts the "${ta}" keyword to the current
* table alias.
*/
public void appendFormulaJoin(String sqlFormulaJoin, SqlJoinType joinType);
void appendFormulaJoin(String sqlFormulaJoin, SqlJoinType joinType);
/**
* Return the current content length.
*/
public int length();
int length();
/**
* Return the current context of the sql context.
*/
public String getContent();
String getContent();
/**
* Return the current join node.
*/
public String peekJoin();
String peekJoin();
/**
* Push a join node onto the stack.
*/
public void pushJoin(String prefix);
void pushJoin(String prefix);
/**
* Pop a join node off the stack.
*/
public void popJoin();
void popJoin();
/**
* Return a table alias without many where clause joins. Typically this is for
* the select clause (fetch joins).
*/
public String getTableAlias(String prefix);
String getTableAlias(String prefix);
/**
* Return a table alias that takes into account many where joins.
*/
public String getTableAliasManyWhere(String prefix);
String getTableAliasManyWhere(String prefix);
public String getRelativePrefix(String propName);
String getRelativePrefix(String propName);
}
@@ -87,6 +87,11 @@ public class DeployBeanDescriptor<T> {
* The base database table.
*/
private String baseTable;
private String baseTableAsOf;
private boolean historySupport;
private TableName baseTableFull;
private String[] properties;
@@ -135,6 +140,20 @@ public class DeployBeanDescriptor<T> {
return Modifier.isAbstract(beanType.getModifiers());
}
/**
* Set to true for @History entity beans that have history.
*/
public void setHistorySupport(boolean historySupport) {
this.historySupport = historySupport;
}
/**
* Return true if this is an @History entity bean.
*/
public boolean isHistorySupport() {
return historySupport;
}
public boolean isScalaObject() {
Class<?>[] interfaces = beanType.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
@@ -405,6 +424,10 @@ public class DeployBeanDescriptor<T> {
return baseTable;
}
public String getBaseTableAsOf() {
return baseTableAsOf;
}
/**
* Return the base table with full structure.
*/
@@ -413,12 +436,12 @@ public class DeployBeanDescriptor<T> {
}
/**
* Set the base table. Only properties mapped to the base table are by default
* persisted.
* Set the base table. Only properties mapped to the base table are by default persisted.
*/
public void setBaseTable(TableName baseTableFull) {
public void setBaseTable(TableName baseTableFull, String asOfSuffix) {
this.baseTableFull = baseTableFull;
this.baseTable = baseTableFull == null ? null : baseTableFull.getQualifiedName();
this.baseTableAsOf = baseTable + asOfSuffix;
}
public void sortProperties() {
@@ -215,6 +215,8 @@ public class DeployBeanProperty {
private boolean indexed;
private String indexName;
private boolean excludedFromHistory;
public DeployBeanProperty(DeployBeanDescriptor<?> desc, Class<?> propertyType, ScalarType<?> scalarType, ScalarTypeConverter<?, ?> typeConverter) {
this.desc = desc;
this.propertyType = propertyType;
@@ -912,4 +914,12 @@ public class DeployBeanProperty {
public void setIndexName(String indexName) {
this.indexName = indexName;
}
public boolean isExcludedFromHistory() {
return excludedFromHistory;
}
public void setExcludedFromHistory(boolean excludedFromHistory) {
this.excludedFromHistory = excludedFromHistory;
}
}
@@ -8,6 +8,7 @@ import javax.persistence.MapKey;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import com.avaje.ebean.annotation.HistoryExclude;
import com.avaje.ebean.annotation.PrivateOwned;
import com.avaje.ebean.annotation.Where;
import com.avaje.ebean.bean.BeanCollection.ModifyListenMode;
@@ -65,7 +66,11 @@ public class AnnotationAssocManys extends AnnotationParser {
readToMany(manyToMany, prop);
}
OrderBy orderBy = get(prop, OrderBy.class);
if (get(prop, HistoryExclude.class) != null) {
prop.setExcludedFromHistory(true);
}
OrderBy orderBy = get(prop, OrderBy.class);
if (orderBy != null) {
prop.setFetchOrderBy(orderBy.value());
}
@@ -10,6 +10,7 @@ import javax.persistence.UniqueConstraint;
import com.avaje.ebean.annotation.CacheStrategy;
import com.avaje.ebean.annotation.CacheTuning;
import com.avaje.ebean.annotation.EntityConcurrencyMode;
import com.avaje.ebean.annotation.History;
import com.avaje.ebean.annotation.NamedUpdate;
import com.avaje.ebean.annotation.NamedUpdates;
import com.avaje.ebean.annotation.UpdateMode;
@@ -26,8 +27,11 @@ import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
*/
public class AnnotationClass extends AnnotationParser {
public AnnotationClass(DeployBeanInfo<?> info) {
private final String asOfViewSuffix;
public AnnotationClass(DeployBeanInfo<?> info, String asOfViewSuffix) {
super(info);
this.asOfViewSuffix = asOfViewSuffix;
}
/**
@@ -48,7 +52,7 @@ public class AnnotationClass extends AnnotationParser {
// default the TableName using NamingConvention.
TableName tableName = namingConvention.getTableName(descriptor.getBeanType());
descriptor.setBaseTable(tableName);
descriptor.setBaseTable(tableName, asOfViewSuffix);
}
}
@@ -84,6 +88,11 @@ public class AnnotationClass extends AnnotationParser {
}
}
History history = cls.getAnnotation(History.class);
if (history != null) {
descriptor.setHistorySupport(true);
}
UpdateMode updateMode = cls.getAnnotation(UpdateMode.class);
if (updateMode != null) {
descriptor.setUpdateChangesOnly(updateMode.updateChangesOnly());
@@ -8,6 +8,7 @@ import com.avaje.ebean.annotation.EmbeddedColumns;
import com.avaje.ebean.annotation.Encrypted;
import com.avaje.ebean.annotation.Expose;
import com.avaje.ebean.annotation.Formula;
import com.avaje.ebean.annotation.HistoryExclude;
import com.avaje.ebean.annotation.Index;
import com.avaje.ebean.annotation.JsonIgnore;
import com.avaje.ebean.annotation.UpdatedTimestamp;
@@ -217,6 +218,10 @@ public class AnnotationFields extends AnnotationParser {
generatedPropFactory.setUpdateTimestamp(prop);
}
if (get(prop, HistoryExclude.class) != null) {
prop.setExcludedFromHistory(true);
}
if (validationAnnotations) {
NotNull notNull = get(prop, NotNull.class);
if (notNull != null && isNotNullOnAllValidationGroups(notNull.groups())) {
@@ -8,7 +8,17 @@ import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
*/
public class ReadAnnotations {
/**
/**
* Typically _with_history and when appended to the base table derives the name of
* the view that unions the base table with the history table to support asOf queries.
*/
private final String asOfViewSuffix;
public ReadAnnotations(String asOfViewSuffix) {
this.asOfViewSuffix = asOfViewSuffix;
}
/**
* Read the initial non-relationship annotations included Id and EmbeddedId.
* <p>
* We then have enough to create BeanTables which are used in readAssociations
@@ -18,7 +28,7 @@ public class ReadAnnotations {
public void readInitial(DeployBeanInfo<?> info, boolean eagerFetchLobs){
try {
new AnnotationClass(info).parse();
new AnnotationClass(info, asOfViewSuffix).parse();
new AnnotationFields(info, eagerFetchLobs).parse();
} catch (RuntimeException e){
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.expression;
import java.sql.Timestamp;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -217,6 +218,11 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
throw new RuntimeException("filterMany not allowed on Junction expression list");
}
@Override
public Query<T> asOf(Timestamp asOf) {
return exprList.asOf(asOf);
}
@Override
public Query<T> apply(PathProperties pathProperties) {
return exprList.apply(pathProperties);
@@ -49,6 +49,7 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex
if (parent.isReadOnly() != null) {
query.setReadOnly(parent.isReadOnly());
}
query.asOf(parent.getAsOf());
query.setParentNode(objectGraphNode);
query.setLazyLoadProperty(lazyLoadProperty);
@@ -13,6 +13,7 @@ import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -30,7 +31,8 @@ public class DLoadContext implements LoadContext {
private final Map<String, DLoadManyContext> manyMap = new HashMap<String, DLoadManyContext>();
private final DLoadBeanContext rootBeanContext;
private final Timestamp asOf;
private final Boolean readOnly;
private final boolean excludeBeanCache;
private final int defaultBatchSize;
@@ -56,6 +58,7 @@ public class DLoadContext implements LoadContext {
this.rootDescriptor = request.getBeanDescriptor();
SpiQuery<?> query = request.getQuery();
this.asOf = query.getAsOf();
this.readOnly = query.isReadOnly();
this.excludeBeanCache = Boolean.FALSE.equals(query.isUseBeanCache());
this.useAutofetchManager = query.getAutoFetchManager() != null;
@@ -211,8 +214,12 @@ public class DLoadContext implements LoadContext {
protected Boolean isReadOnly() {
return readOnly;
}
public PersistenceContext getPersistenceContext() {
protected Timestamp getAsOf() {
return asOf;
}
public PersistenceContext getPersistenceContext() {
return persistenceContext;
}
@@ -60,6 +60,8 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex
if (parent.isReadOnly() != null){
query.setReadOnly(parent.isReadOnly());
}
query.asOf(parent.getAsOf());
query.setParentNode(objectGraphNode);
if (queryProps != null){
@@ -23,6 +23,8 @@ import com.avaje.ebeaninternal.server.querydefn.OrmQueryLimitRequest;
import javax.persistence.PersistenceException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
@@ -43,14 +45,20 @@ public class CQueryBuilder implements Constants {
private final boolean selectCountWithAlias;
private final Map<String,String> asOfTableMapping;
private final String asOfSysPeriod;
private DatabasePlatform dbPlatform;
/**
* Create the SqlGenSelect.
*/
public CQueryBuilder(DatabasePlatform dbPlatform, Binder binder) {
public CQueryBuilder(DatabasePlatform dbPlatform, Binder binder, Map<String,String> asOfTableMapping, String asOfSysPeriod) {
this.binder = binder;
this.asOfTableMapping = asOfTableMapping;
this.asOfSysPeriod = asOfSysPeriod;
this.tableAliasPlaceHolder = dbPlatform.getTableAliasPlaceHolder();
this.columnAliasPrefix = dbPlatform.getColumnAliasPrefix();
this.sqlSelectBuilder = new RawSqlSelectClauseBuilder(dbPlatform, binder);
@@ -103,7 +111,8 @@ public class CQueryBuilder implements Constants {
// use RawSql or generated Sql
predicates.prepare(true);
SqlTree sqlTree = createSqlTree(request, predicates);
Map<String,String> asOfMap = query.isAsOfQuery() ? asOfTableMapping : null;
SqlTree sqlTree = createSqlTree(request, predicates, asOfMap);
SqlLimitResponse s = buildSql(null, request, predicates, sqlTree);
String sql = s.getSql();
@@ -155,7 +164,8 @@ public class CQueryBuilder implements Constants {
predicates.prepare(true);
SqlTree sqlTree = createSqlTree(request, predicates);
Map<String,String> asOfMap = query.isAsOfQuery() ? asOfTableMapping : null;
SqlTree sqlTree = createSqlTree(request, predicates, asOfMap);
SqlLimitResponse s = buildSql(sqlSelect, request, predicates, sqlTree);
String sql = s.getSql();
if (hasMany || query.isRawSql()) {
@@ -203,7 +213,14 @@ public class CQueryBuilder implements Constants {
predicates.prepare(true);
// Build the tree structure that represents the query.
SqlTree sqlTree = createSqlTree(request, predicates);
SpiQuery<T> query = request.getQuery();
Map<String,String> asOfMap = query.isAsOfQuery() ? asOfTableMapping : null;
SqlTree sqlTree = createSqlTree(request, predicates, asOfMap);
if (query.isAsOfQuery()) {
sqlTree.addAsOfTableAlias(query);
}
SqlLimitResponse res = buildSql(null, request, predicates, sqlTree);
boolean rawSql = request.isRawSql();
@@ -232,13 +249,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) {
private SqlTree createSqlTree(OrmQueryRequest<?> request, CQueryPredicates predicates, Map<String,String> withHistoryTables) {
if (request.isRawSql()) {
return createRawSqlSqlTree(request, predicates);
}
return new SqlTreeBuilder(tableAliasPlaceHolder, columnAliasPrefix, request, predicates).build();
return new SqlTreeBuilder(tableAliasPlaceHolder, columnAliasPrefix, request, predicates, withHistoryTables).build();
}
private SqlTree createRawSqlSqlTree(OrmQueryRequest<?> request, CQueryPredicates predicates) {
@@ -380,6 +397,7 @@ public class CQueryBuilder implements Constants {
String dbFilterMany = predicates.getDbFilterMany();
if (!isEmpty(dbFilterMany)) {
if (!hasWhere) {
hasWhere = true;
sb.append(" where ");
} else {
sb.append("and ");
@@ -387,7 +405,23 @@ public class CQueryBuilder implements Constants {
sb.append(dbFilterMany);
}
List<String> asOfTableAlias = query.getAsOfTableAlias();
if (asOfTableAlias != null) {
// append the effective date predicates for each table alias
// that maps to a @History entity involved in this query
if (!hasWhere) {
sb.append(" where ");
} else {
sb.append("and ");
}
for (int i = 0; i < asOfTableAlias.size(); i++) {
if (i > 0) {
sb.append(" and ");
}
sb.append(dbPlatform.getAsOfPredicate(asOfTableAlias.get(i), asOfSysPeriod));
}
}
if (dbOrderBy != null) {
sb.append(" order by ").append(dbOrderBy);
}
@@ -1,6 +1,7 @@
package com.avaje.ebeaninternal.server.query;
import java.sql.SQLException;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -31,9 +32,9 @@ public class CQueryEngine {
private final CQueryBuilder queryBuilder;
public CQueryEngine(DatabasePlatform dbPlatform, Binder binder) {
public CQueryEngine(DatabasePlatform dbPlatform, Binder binder, Map<String,String> asOfTableMapping, String asOfSysPeriod) {
this.forwardOnlyHintOnFindIterate = dbPlatform.isForwardOnlyHintOnFindIterate();
this.queryBuilder = new CQueryBuilder(dbPlatform, binder);
this.queryBuilder = new CQueryBuilder(dbPlatform, binder, asOfTableMapping, asOfSysPeriod);
}
public <T> CQuery<T> buildQuery(OrmQueryRequest<T> request) {
@@ -1,8 +1,10 @@
package com.avaje.ebeaninternal.server.query;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.avaje.ebean.RawSql;
@@ -174,6 +176,17 @@ public class CQueryPredicates {
}
}
List<String> historyTableAlias = query.getAsOfTableAlias();
if (historyTableAlias != null) {
// bind the asAt value for each table alias
// there is one effective date predicate per table alias
Timestamp asOf = query.getAsOf();
bindLog.append(" asOf ").append(asOf);
for (int i = 0; i < historyTableAlias.size(); i++) {
binder.bindObject(dataBind, asOf);
}
}
if (havingNamedParams != null) {
// bind named parameters in having...
bindLog.append(" havingNamed ");
@@ -1,14 +1,15 @@
package com.avaje.ebeaninternal.server.query;
import java.util.ArrayList;
import java.util.HashSet;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.DbSqlContext;
import com.avaje.ebeaninternal.server.deploy.TableJoinColumn;
import com.avaje.ebeaninternal.server.lib.util.StringHelper;
import com.avaje.ebeaninternal.server.util.ArrayStack;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Map;
public class DefaultDbSqlContext implements DbSqlContext {
private static final String COMMA = ", ";
@@ -45,25 +46,22 @@ public class DefaultDbSqlContext implements DbSqlContext {
private ArrayList<BeanProperty> encryptedProps;
/**
* Construct for FROM clause (no column alias used).
*/
public DefaultDbSqlContext(SqlTreeAlias alias, String tableAliasPlaceHolder) {
this.tableAliasPlaceHolder = tableAliasPlaceHolder;
this.columnAliasPrefix = null;
this.useColumnAlias = false;
this.alias = alias;
}
private final Map<String,String> asOfTableMap;
private final boolean asOfQuery;
/**
* Construct for SELECT clause (with column alias settings).
*/
public DefaultDbSqlContext(SqlTreeAlias alias, String tableAliasPlaceHolder,
String columnAliasPrefix, boolean alwaysUseColumnAlias) {
String columnAliasPrefix, boolean alwaysUseColumnAlias, Map<String,String> asOfTableMap) {
this.alias = alias;
this.tableAliasPlaceHolder = tableAliasPlaceHolder;
this.columnAliasPrefix = columnAliasPrefix;
this.useColumnAlias = alwaysUseColumnAlias;
this.asOfTableMap = asOfTableMap;
this.asOfQuery = (asOfTableMap != null);
}
public void addEncryptedProp(BeanProperty p) {
@@ -108,7 +106,21 @@ public class DefaultDbSqlContext implements DbSqlContext {
sb.append(" ");
sb.append(type);
sb.append(" ").append(table).append(" ");
if (!asOfQuery) {
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);
if (withHistoryTable != null) {
// there is an associated history table and view so use that
sb.append(" ").append(withHistoryTable).append(" ");
} else {
sb.append(" ").append(table).append(" ");
}
}
sb.append(a2);
sb.append(" on ");
@@ -222,7 +234,7 @@ public class DefaultDbSqlContext implements DbSqlContext {
public void appendColumn(String tableAlias, String column) {
sb.append(COMMA);
if (column.indexOf("${}") > -1) {
if (column.contains("${}")) {
// support DB functions such as lower() etc
// with the use of secondary columns
String x = StringHelper.replaceString(column, "${}", tableAlias);
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.query;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
@@ -80,6 +81,13 @@ public class SqlTree {
this.includes = null;
}
/**
* Recurse through the tree adding an table alias' for @History entity beans.
*/
public void addAsOfTableAlias(SpiQuery<?> query) {
rootNode.addAsOfTableAlias(query);
}
/**
* Build a select expression chain for RawSql.
*/
@@ -98,7 +98,7 @@ public class SqlTreeBuilder {
* to the root node.
*/
public SqlTreeBuilder(String tableAliasPlaceHolder, String columnAliasPrefix,
OrmQueryRequest<?> request, CQueryPredicates predicates) {
OrmQueryRequest<?> request, CQueryPredicates predicates, Map<String,String> asOfTables) {
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);
this.ctx = new DefaultDbSqlContext(alias, tableAliasPlaceHolder, columnAliasPrefix, !subQuery, asOfTables);
}
/**
@@ -265,7 +265,8 @@ public class SqlTreeBuilder {
// Optional many property for lazy loading query
BeanPropertyAssocMany<?> lazyLoadMany = (query == null) ? null : query.getLazyLoadForParentsProperty();
boolean withId = !rawNoId && !subQuery && (query == null || !query.isDistinct());
return new SqlTreeNodeRoot(desc, props, myList, withId, includeJoin, lazyLoadMany);
return new SqlTreeNodeRoot(desc, props, myList, withId, includeJoin, lazyLoadMany, SpiQuery.TemporalMode.of(query));
} else if (prop instanceof BeanPropertyAssocMany<?>) {
return new SqlTreeNodeManyRoot(prefix, (BeanPropertyAssocMany<?>) prop, props, myList);
@@ -1,6 +1,7 @@
package com.avaje.ebeaninternal.server.query;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.deploy.DbReadContext;
import com.avaje.ebeaninternal.server.deploy.DbSqlContext;
@@ -32,6 +33,11 @@ public interface SqlTreeNode {
*/
void appendWhere(DbSqlContext ctx);
/**
* Recurse through the tree adding an table alias' for @History entity beans.
*/
void addAsOfTableAlias(SpiQuery<?> query);
/**
* Load the appropriate information from the SqlSelectReader.
* <p>
@@ -9,6 +9,7 @@ import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.EntityBeanIntercept;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.api.SpiQuery.Mode;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
@@ -70,25 +71,36 @@ public class SqlTreeNodeBean implements SqlTreeNode {
protected final BeanPropertyAssocMany<?> lazyLoadParent;
protected final SpiQuery.TemporalMode temporalMode;
private final IdBinder lazyLoadParentIdBinder;
protected String baseTableAlias;
/**
* Table alias set if this bean node includes a join to a intersection
* table and that table has history support.
*/
protected String intersectionAsOfTableAlias;
public SqlTreeNodeBean(String prefix, BeanPropertyAssoc<?> beanProp, SqlTreeProperties props,
List<SqlTreeNode> myChildren, boolean withId) {
this(prefix, beanProp, beanProp.getTargetDescriptor(), props, myChildren, withId, null);
this(prefix, beanProp, beanProp.getTargetDescriptor(), props, myChildren, withId, null, SpiQuery.TemporalMode.CURRENT);
}
/**
* Create with the appropriate node.
*/
public SqlTreeNodeBean(String prefix, BeanPropertyAssoc<?> beanProp, BeanDescriptor<?> desc,
SqlTreeProperties props, List<SqlTreeNode> myChildren, boolean withId, BeanPropertyAssocMany<?> lazyLoadParent) {
SqlTreeProperties props, List<SqlTreeNode> myChildren, boolean withId, BeanPropertyAssocMany<?> lazyLoadParent, SpiQuery.TemporalMode temporalMode) {
this.lazyLoadParent = lazyLoadParent;
this.lazyLoadParentIdBinder = (lazyLoadParent == null) ? null : lazyLoadParent.getBeanDescriptor().getIdBinder();
this.prefix = prefix;
this.nodeBeanProp = beanProp;
this.desc = desc;
this.temporalMode = temporalMode;
this.inheritInfo = desc.getInheritInfo();
this.extraWhere = (beanProp == null) ? null : beanProp.getExtraWhere();
@@ -441,6 +453,8 @@ public class SqlTreeNodeBean implements SqlTreeNode {
ctx.pushJoin(prefix);
ctx.pushTableAlias(prefix);
baseTableAlias = ctx.getTableAlias(prefix);
// join and return SqlJoinType to use for child joins
joinType = appendFromBaseTable(ctx, joinType);
@@ -457,6 +471,21 @@ public class SqlTreeNodeBean implements SqlTreeNode {
ctx.popJoin();
}
public void addAsOfTableAlias(SpiQuery<?> query) {
// if history on this bean type add it's alias
// for each alias we add an effect date predicate
if (desc.isHistorySupport()) {
query.addAsOfTableAlias(baseTableAlias);
}
if (intersectionAsOfTableAlias != null) {
// adds the 'as of' predicate for this intersection table
query.addAsOfTableAlias(intersectionAsOfTableAlias);
}
for (int i = 0; i < children.length; i++) {
children[i].addAsOfTableAlias(query);
}
}
/**
* Join to base table for this node. This includes a join to the intersection
* table if this is a ManyToMany node.
@@ -472,8 +501,12 @@ public class SqlTreeNodeBean implements SqlTreeNode {
String parentAlias = ctx.getTableAlias(split[0]);
String alias2 = alias + "z_";
// adding the additional join to the intersection table
TableJoin manyToManyJoin = manyProp.getIntersectionTableJoin();
manyToManyJoin.addJoin(joinType, parentAlias, alias2, ctx);
if (!manyProp.isExcludedFromHistory()) {
intersectionAsOfTableAlias = alias2;
}
return nodeBeanProp.addJoin(joinType, alias2, alias, ctx);
}
@@ -1,6 +1,7 @@
package com.avaje.ebeaninternal.server.query;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.deploy.DbReadContext;
@@ -40,6 +41,11 @@ public class SqlTreeNodeExtraJoin implements SqlTreeNode {
// nothing to add
}
@Override
public void addAsOfTableAlias(SpiQuery<?> query) {
// nothing to do here
}
/**
* Return true if the extra join is a many join.
* <p>
@@ -13,7 +13,7 @@ public final class SqlTreeNodeManyRoot extends SqlTreeNodeBean {
final BeanPropertyAssocMany<?> manyProp;
public SqlTreeNodeManyRoot(String prefix, BeanPropertyAssocMany<?> prop, SqlTreeProperties props, List<SqlTreeNode> myList) {
super(prefix, prop, prop.getTargetDescriptor(), props, myList, true, null);
super(prefix, prop, prop.getTargetDescriptor(), props, myList, true, null, null);
this.manyProp = prop;
}
@@ -1,6 +1,7 @@
package com.avaje.ebeaninternal.server.query;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne;
@@ -37,6 +38,11 @@ public class SqlTreeNodeManyWhereJoin implements SqlTreeNode {
this.parentPrefix = split[0];
}
@Override
public void addAsOfTableAlias(SpiQuery<?> query) {
// do nothing here ...
}
/**
* Append to the FROM clause for this node.
*/
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.query;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.deploy.DbSqlContext;
@@ -17,13 +18,14 @@ public final class SqlTreeNodeRoot extends SqlTreeNodeBean {
/**
* Specify for SqlSelect to include an Id property or not.
*/
public SqlTreeNodeRoot(BeanDescriptor<?> desc, SqlTreeProperties props, List<SqlTreeNode> myList, boolean withId, TableJoin includeJoin, BeanPropertyAssocMany<?> many) {
super(null, null, desc, props, myList, withId, many);
public SqlTreeNodeRoot(BeanDescriptor<?> desc, SqlTreeProperties props, List<SqlTreeNode> myList, boolean withId,
TableJoin includeJoin, BeanPropertyAssocMany<?> many, SpiQuery.TemporalMode temporalMode) {
super(null, null, desc, props, myList, withId, many, temporalMode);
this.includeJoin = includeJoin;
}
public SqlTreeNodeRoot(BeanDescriptor<?> desc, SqlTreeProperties props, List<SqlTreeNode> myList, boolean withId) {
super(null, null, desc, props, myList, withId, null);
super(null, null, desc, props, myList, withId, null, null);
this.includeJoin = null;
}
@@ -33,11 +35,11 @@ public final class SqlTreeNodeRoot extends SqlTreeNodeBean {
@Override
public SqlJoinType appendFromBaseTable(DbSqlContext ctx, SqlJoinType joinType) {
ctx.append(desc.getBaseTable());
ctx.append(" ").append(ctx.getTableAlias(null));
ctx.append(desc.getBaseTable(temporalMode));
ctx.append(" ").append(baseTableAlias);
if (includeJoin != null) {
String a1 = ctx.getTableAlias(null);
String a1 = baseTableAlias;
String a2 = "int_"; // unique alias for intersection join
includeJoin.addJoin(joinType, a1, a2, ctx);
}
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.querydefn;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@@ -12,7 +13,6 @@ import com.avaje.ebean.*;
import com.avaje.ebean.OrderBy.Property;
import com.avaje.ebean.bean.BeanCollectionTouched;
import com.avaje.ebean.bean.CallStack;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.bean.ObjectGraphOrigin;
import com.avaje.ebean.bean.PersistenceContext;
@@ -153,6 +153,16 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
private DefaultExpressionList<T> havingExpressions;
/**
* The list of table alias associated with @History entity beans.
*/
private List<String> asOfTableAlias;
/**
* Set for flashback style 'as of' query.
*/
private Timestamp asOf;
private int bufferFetchSizeHint;
private boolean usageProfiling = true;
@@ -258,6 +268,30 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
return this;
}
/**
* This table alias is for a @History entity involved in the query and as
* such we need to add a 'as of predicate' to the query using this alias.
*/
public void addAsOfTableAlias(String tableAlias) {
if (asOfTableAlias == null) {
asOfTableAlias = new ArrayList<String>();
}
asOfTableAlias.add(tableAlias);
}
public List<String> getAsOfTableAlias() {
return asOfTableAlias;
}
public Timestamp getAsOf() {
return asOf;
}
public DefaultOrmQuery<T> asOf(Timestamp asOfDateTime) {
this.asOf = asOfDateTime;
return this;
}
/**
* Set the BeanDescriptor for the root type of this query.
*/
@@ -581,6 +615,15 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
return mode;
}
@Override
public TemporalMode getTemporalMode() {
return asOf == null ? TemporalMode.CURRENT : TemporalMode.AS_OF;
}
public boolean isAsOfQuery() {
return asOf != null;
}
public void setMode(Mode mode) {
this.mode = mode;
}
@@ -665,6 +708,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
builder.add(rawWhereClause).add(additionalWhere).add(additionalHaving);
builder.add(mapKey);
builder.add(id != null);
builder.add(asOf != null);
builder.add(rawSql == null ? 0 : rawSql.queryHash());
builder.add(includeTableJoin != null ? includeTableJoin.queryHash() : 0);
builder.add(rootTableAlias);
@@ -735,6 +779,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
hc = hc * 31 + (whereExpressions == null ? 0 : whereExpressions.queryBindHash());
hc = hc * 31 + (havingExpressions == null ? 0 : havingExpressions.queryBindHash());
hc = hc * 31 + (bindParams == null ? 0 : bindParams.queryBindHash());
hc = hc * 31 + (asOf == null ? 0 : asOf.hashCode());
return hc;
}
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.util;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -107,6 +108,11 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
return query;
}
@Override
public Query<T> asOf(Timestamp asOf) {
return query.asOf(asOf);
}
@Override
public ExpressionList<T> where() {
return query.where();