diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractCollectionMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractCollectionMapper.java index c8781fa040..ece4d1ae6b 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractCollectionMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractCollectionMapper.java @@ -200,14 +200,22 @@ public abstract class AbstractCollectionMapper implements PropertyMapper { protected abstract Initializor getInitializor(AuditConfiguration verCfg, AuditReaderImplementor versionsReader, Object primaryKey, - Number revision); + Number revision, boolean removed); public void mapToEntityFromMap(AuditConfiguration verCfg, Object obj, Map data, Object primaryKey, AuditReaderImplementor versionsReader, Number revision) { - Setter setter = ReflectionTools.getSetter(obj.getClass(), - commonCollectionMapperData.getCollectionReferencingPropertyData()); + Setter setter = ReflectionTools.getSetter(obj.getClass(), commonCollectionMapperData.getCollectionReferencingPropertyData()); try { - setter.set(obj, proxyConstructor.newInstance(getInitializor(verCfg, versionsReader, primaryKey, revision)), null); + setter.set( + obj, + proxyConstructor.newInstance( + getInitializor( + verCfg, versionsReader, primaryKey, revision, + RevisionType.DEL.equals( data.get( verCfg.getAuditEntCfg().getRevisionTypePropName() ) ) + ) + ), + null + ); } catch (InstantiationException e) { throw new AuditException(e); } catch (IllegalAccessException e) { diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/BasicCollectionMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/BasicCollectionMapper.java index f32634eaec..c3062e3aae 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/BasicCollectionMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/BasicCollectionMapper.java @@ -49,9 +49,9 @@ public class BasicCollectionMapper extends AbstractCollect } protected Initializor getInitializor(AuditConfiguration verCfg, AuditReaderImplementor versionsReader, - Object primaryKey, Number revision) { + Object primaryKey, Number revision, boolean removed) { return new BasicCollectionInitializor(verCfg, versionsReader, commonCollectionMapperData.getQueryGenerator(), - primaryKey, revision, collectionClass, elementComponentData); + primaryKey, revision, removed, collectionClass, elementComponentData); } protected Collection getNewCollectionContent(PersistentCollection newCollection) { diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/ListCollectionMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/ListCollectionMapper.java index a85c811528..ffca9fccc9 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/ListCollectionMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/ListCollectionMapper.java @@ -54,9 +54,9 @@ public final class ListCollectionMapper extends AbstractCollectionMapper i } protected Initializor getInitializor(AuditConfiguration verCfg, AuditReaderImplementor versionsReader, - Object primaryKey, Number revision) { + Object primaryKey, Number revision, boolean removed) { return new ListCollectionInitializor(verCfg, versionsReader, commonCollectionMapperData.getQueryGenerator(), - primaryKey, revision, elementComponentData, indexComponentData); + primaryKey, revision, removed, elementComponentData, indexComponentData); } @SuppressWarnings({"unchecked"}) diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/MapCollectionMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/MapCollectionMapper.java index f24f5f419b..d20a1bc890 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/MapCollectionMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/MapCollectionMapper.java @@ -52,9 +52,9 @@ public class MapCollectionMapper extends AbstractCollectionMapper } protected Initializor getInitializor(AuditConfiguration verCfg, AuditReaderImplementor versionsReader, - Object primaryKey, Number revision) { + Object primaryKey, Number revision, boolean removed) { return new MapCollectionInitializor(verCfg, versionsReader, commonCollectionMapperData.getQueryGenerator(), - primaryKey, revision, collectionClass, elementComponentData, indexComponentData); + primaryKey, revision, removed, collectionClass, elementComponentData, indexComponentData); } protected Collection getNewCollectionContent(PersistentCollection newCollection) { diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/SortedMapCollectionMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/SortedMapCollectionMapper.java index a3a512ebd1..a02683607c 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/SortedMapCollectionMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/SortedMapCollectionMapper.java @@ -46,9 +46,9 @@ public final class SortedMapCollectionMapper extends MapCollectionMapper getInitializor(AuditConfiguration verCfg, AuditReaderImplementor versionsReader, - Object primaryKey, Number revision) { + Object primaryKey, Number revision, boolean removed) { return new SortedMapCollectionInitializor(verCfg, versionsReader, commonCollectionMapperData.getQueryGenerator(), - primaryKey, revision, collectionClass, elementComponentData, indexComponentData, comparator); + primaryKey, revision, removed, collectionClass, elementComponentData, indexComponentData, comparator); } } \ No newline at end of file diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/SortedSetCollectionMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/SortedSetCollectionMapper.java index abf88ccb89..7c80ffe525 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/SortedSetCollectionMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/SortedSetCollectionMapper.java @@ -46,9 +46,8 @@ public final class SortedSetCollectionMapper extends BasicCollectionMapper getInitializor(AuditConfiguration verCfg, AuditReaderImplementor versionsReader, - Object primaryKey, Number revision) { + Object primaryKey, Number revision, boolean removed) { return new SortedSetCollectionInitializor(verCfg, versionsReader, commonCollectionMapperData.getQueryGenerator(), - primaryKey, revision, collectionClass, elementComponentData, comparator); + primaryKey, revision, removed, collectionClass, elementComponentData, comparator); } - } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/AbstractCollectionInitializor.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/AbstractCollectionInitializor.java index 635b9a2720..d9edea9c7b 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/AbstractCollectionInitializor.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/AbstractCollectionInitializor.java @@ -37,18 +37,19 @@ public abstract class AbstractCollectionInitializor implements Initializor private final AuditReaderImplementor versionsReader; private final RelationQueryGenerator queryGenerator; private final Object primaryKey; - protected final Number revision; + protected final boolean removed; protected final EntityInstantiator entityInstantiator; public AbstractCollectionInitializor(AuditConfiguration verCfg, AuditReaderImplementor versionsReader, RelationQueryGenerator queryGenerator, - Object primaryKey, Number revision) { + Object primaryKey, Number revision, boolean removed) { this.versionsReader = versionsReader; this.queryGenerator = queryGenerator; this.primaryKey = primaryKey; this.revision = revision; + this.removed = removed; entityInstantiator = new EntityInstantiator(verCfg, versionsReader); } @@ -58,7 +59,7 @@ public abstract class AbstractCollectionInitializor implements Initializor protected abstract void addToCollection(T collection, Object collectionRow); public T initialize() { - List collectionContent = queryGenerator.getQuery(versionsReader, primaryKey, revision).list(); + List collectionContent = queryGenerator.getQuery(versionsReader, primaryKey, revision, removed).list(); T collection = initializeCollection(collectionContent.size()); diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/ArrayCollectionInitializor.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/ArrayCollectionInitializor.java index 91c2888ce5..37d809bc87 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/ArrayCollectionInitializor.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/ArrayCollectionInitializor.java @@ -41,10 +41,10 @@ public class ArrayCollectionInitializor extends AbstractCollectionInitializor extends AbstractCo public BasicCollectionInitializor(AuditConfiguration verCfg, AuditReaderImplementor versionsReader, RelationQueryGenerator queryGenerator, - Object primaryKey, Number revision, + Object primaryKey, Number revision, boolean removed, Class collectionClass, MiddleComponentData elementComponentData) { - super(verCfg, versionsReader, queryGenerator, primaryKey, revision); + super(verCfg, versionsReader, queryGenerator, primaryKey, revision, removed); this.collectionClass = collectionClass; this.elementComponentData = elementComponentData; diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/ListCollectionInitializor.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/ListCollectionInitializor.java index edee51b780..b8147e2d86 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/ListCollectionInitializor.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/ListCollectionInitializor.java @@ -42,10 +42,10 @@ public class ListCollectionInitializor extends AbstractCollectionInitializor extends AbstractCollectionI public MapCollectionInitializor(AuditConfiguration verCfg, AuditReaderImplementor versionsReader, RelationQueryGenerator queryGenerator, - Object primaryKey, Number revision, + Object primaryKey, Number revision, boolean removed, Class collectionClass, MiddleComponentData elementComponentData, MiddleComponentData indexComponentData) { - super(verCfg, versionsReader, queryGenerator, primaryKey, revision); + super(verCfg, versionsReader, queryGenerator, primaryKey, revision, removed); this.collectionClass = collectionClass; this.elementComponentData = elementComponentData; diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/SortedMapCollectionInitializor.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/SortedMapCollectionInitializor.java index 021432184c..34c810ce8e 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/SortedMapCollectionInitializor.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/SortedMapCollectionInitializor.java @@ -44,11 +44,11 @@ public class SortedMapCollectionInitializor extends MapCollectionInitializor collectionClass, MiddleComponentData elementComponentData, MiddleComponentData indexComponentData, Comparator comparator) { - super(verCfg, versionsReader, queryGenerator, primaryKey, revision, collectionClass, elementComponentData, indexComponentData); + super(verCfg, versionsReader, queryGenerator, primaryKey, revision, removed, collectionClass, elementComponentData, indexComponentData); this.comparator = comparator; } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/SortedSetCollectionInitializor.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/SortedSetCollectionInitializor.java index 21f6bb32a8..8c5ff97dce 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/SortedSetCollectionInitializor.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/lazy/initializor/SortedSetCollectionInitializor.java @@ -41,8 +41,11 @@ import org.hibernate.envers.reader.AuditReaderImplementor; public class SortedSetCollectionInitializor extends BasicCollectionInitializor { private final Comparator comparator; - public SortedSetCollectionInitializor(AuditConfiguration verCfg, AuditReaderImplementor versionsReader, RelationQueryGenerator queryGenerator, Object primaryKey, Number revision, Class collectionClass, MiddleComponentData elementComponentData, Comparator comparator) { - super(verCfg, versionsReader, queryGenerator, primaryKey, revision, collectionClass, elementComponentData); + public SortedSetCollectionInitializor(AuditConfiguration verCfg, AuditReaderImplementor versionsReader, + RelationQueryGenerator queryGenerator, Object primaryKey, Number revision, boolean removed, + Class collectionClass, MiddleComponentData elementComponentData, + Comparator comparator) { + super(verCfg, versionsReader, queryGenerator, primaryKey, revision, removed, collectionClass, elementComponentData); this.comparator = comparator; } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/AbstractRelationQueryGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/AbstractRelationQueryGenerator.java index cb3fe4a696..e465507092 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/AbstractRelationQueryGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/AbstractRelationQueryGenerator.java @@ -23,12 +23,16 @@ */ package org.hibernate.envers.entities.mapper.relation.query; +import java.util.Collections; +import java.util.Map; + import org.hibernate.Query; import org.hibernate.envers.RevisionType; import org.hibernate.envers.configuration.AuditEntitiesConfiguration; import org.hibernate.envers.entities.mapper.id.QueryParameterData; import org.hibernate.envers.entities.mapper.relation.MiddleIdData; import org.hibernate.envers.reader.AuditReaderImplementor; +import org.hibernate.envers.tools.query.QueryBuilder; import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants.DEL_REVISION_TYPE_PARAMETER; import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants.REVISION_PARAMETER; @@ -50,12 +54,22 @@ public abstract class AbstractRelationQueryGenerator implements RelationQueryGen this.revisionTypeInId = revisionTypeInId; } + /** + * @return Query used to retrieve state of audited entity valid at a given revision. + */ protected abstract String getQueryString(); - public Query getQuery(AuditReaderImplementor versionsReader, Object primaryKey, Number revision) { - Query query = versionsReader.getSession().createQuery( getQueryString() ); - query.setParameter( REVISION_PARAMETER, revision ); + /** + * @return Query executed to retrieve state of audited entity valid at previous revision + * or removed during exactly specified revision number. Used only when traversing deleted + * entities graph. + */ + protected abstract String getQueryRemovedString(); + + public Query getQuery(AuditReaderImplementor versionsReader, Object primaryKey, Number revision, boolean removed) { + Query query = versionsReader.getSession().createQuery( removed ? getQueryRemovedString() : getQueryString() ); query.setParameter( DEL_REVISION_TYPE_PARAMETER, RevisionType.DEL ); + query.setParameter( REVISION_PARAMETER, revision ); for ( QueryParameterData paramData : referencingIdData.getPrefixedMapper().mapToQueryParametersFromId( primaryKey ) ) { paramData.setParameterValue( query ); } @@ -67,4 +81,14 @@ public abstract class AbstractRelationQueryGenerator implements RelationQueryGen ? verEntCfg.getOriginalIdPropName() + "." + verEntCfg.getRevisionTypePropName() : verEntCfg.getRevisionTypePropName(); } -} + + protected String queryToString(QueryBuilder query) { + return queryToString( query, Collections.emptyMap() ); + } + + protected String queryToString(QueryBuilder query, Map queryParamValues) { + final StringBuilder sb = new StringBuilder(); + query.build( sb, queryParamValues ); + return sb.toString(); + } +} \ No newline at end of file diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/OneAuditEntityQueryGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/OneAuditEntityQueryGenerator.java index 953be5ee0d..90d6b46e2d 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/OneAuditEntityQueryGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/OneAuditEntityQueryGenerator.java @@ -23,18 +23,12 @@ */ package org.hibernate.envers.entities.mapper.relation.query; -import java.util.Collections; - -import org.hibernate.Query; -import org.hibernate.envers.RevisionType; import org.hibernate.envers.configuration.AuditEntitiesConfiguration; import org.hibernate.envers.configuration.GlobalConfiguration; -import org.hibernate.envers.entities.mapper.id.QueryParameterData; import org.hibernate.envers.entities.mapper.relation.MiddleIdData; -import org.hibernate.envers.reader.AuditReaderImplementor; -import org.hibernate.envers.strategy.AuditStrategy; import org.hibernate.envers.tools.query.Parameters; import org.hibernate.envers.tools.query.QueryBuilder; +import org.hibernate.envers.strategy.AuditStrategy; import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants.DEL_REVISION_TYPE_PARAMETER; import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants.REFERENCED_ENTITY_ALIAS; @@ -43,65 +37,104 @@ import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants /** * Selects data from an audit entity. + * * @author Adam Warski (adam at warski dot org) + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) */ public final class OneAuditEntityQueryGenerator extends AbstractRelationQueryGenerator { - private final String queryString; + private final String queryString; + private final String queryRemovedString; - public OneAuditEntityQueryGenerator(GlobalConfiguration globalCfg, AuditEntitiesConfiguration verEntCfg, - AuditStrategy auditStrategy, - MiddleIdData referencingIdData, - String referencedEntityName, MiddleIdData referencedIdData, - boolean revisionTypeInId) { + public OneAuditEntityQueryGenerator(GlobalConfiguration globalCfg, AuditEntitiesConfiguration verEntCfg, + AuditStrategy auditStrategy, MiddleIdData referencingIdData, + String referencedEntityName, MiddleIdData referencedIdData, boolean revisionTypeInId) { super( verEntCfg, referencingIdData, revisionTypeInId ); - /* - * The query that we need to create: - * SELECT e FROM versionsReferencedEntity e - * WHERE - * (only entities referenced by the association; id_ref_ing = id of the referencing entity) - * e.id_ref_ing = :id_ref_ing AND - * (selecting e entities at revision :revision) - * --> for DefaultAuditStrategy: - * e.revision = (SELECT max(e2.revision) FROM versionsReferencedEntity e2 - * WHERE e2.revision <= :revision AND e2.id = e.id) - * - * --> for ValidityAuditStrategy: - * e.revision <= :revision and (e.endRevision > :revision or e.endRevision is null) - * - * AND - * (only non-deleted entities) - * e.revision_type != DEL - */ - String revisionPropertyPath = verEntCfg.getRevisionNumberPath(); - String originalIdPropertyName = verEntCfg.getOriginalIdPropName(); + /* + * The valid query that we need to create: + * SELECT e FROM versionsReferencedEntity e + * WHERE + * (only entities referenced by the association; id_ref_ing = id of the referencing entity) + * e.id_ref_ing = :id_ref_ing AND + * (selecting e entities at revision :revision) + * --> for DefaultAuditStrategy: + * e.revision = (SELECT max(e2.revision) FROM versionsReferencedEntity e2 + * WHERE e2.revision <= :revision AND e2.id = e.id) + * + * --> for ValidityAuditStrategy: + * e.revision <= :revision and (e.endRevision > :revision or e.endRevision is null) + * + * AND + * (only non-deleted entities) + * e.revision_type != DEL + */ + final QueryBuilder commonPart = commonQueryPart( verEntCfg.getAuditEntityName( referencedEntityName ) ); + final QueryBuilder validQuery = commonPart.deepCopy(); + final QueryBuilder removedQuery = commonPart.deepCopy(); + createValidDataRestrictions( + globalCfg, auditStrategy, referencedIdData, validQuery, validQuery.getRootParameters(), true + ); + createValidAndRemovedDataRestrictions( globalCfg, auditStrategy, referencedIdData, removedQuery ); - String versionsReferencedEntityName = verEntCfg.getAuditEntityName(referencedEntityName); + queryString = queryToString( validQuery ); + queryRemovedString = queryToString( removedQuery ); + } - // SELECT e FROM versionsEntity e - QueryBuilder qb = new QueryBuilder(versionsReferencedEntityName, REFERENCED_ENTITY_ALIAS); - qb.addProjection(null, REFERENCED_ENTITY_ALIAS, false, false); - // WHERE - Parameters rootParameters = qb.getRootParameters(); - // e.id_ref_ed = :id_ref_ed - referencingIdData.getPrefixedMapper().addNamedIdEqualsToQuery(rootParameters, null, true); + /** + * Compute common part for both queries. + */ + private QueryBuilder commonQueryPart(String versionsReferencedEntityName) { + // SELECT e FROM versionsEntity e + final QueryBuilder qb = new QueryBuilder( versionsReferencedEntityName, REFERENCED_ENTITY_ALIAS ); + qb.addProjection( null, REFERENCED_ENTITY_ALIAS, false, false ); + // WHERE + // e.id_ref_ed = :id_ref_ed + referencingIdData.getPrefixedMapper().addNamedIdEqualsToQuery( qb.getRootParameters(), null, true ); + return qb; + } - // (selecting e entities at revision :revision) - // --> based on auditStrategy (see above) - auditStrategy.addEntityAtRevisionRestriction(globalCfg, qb, revisionPropertyPath, - verEntCfg.getRevisionEndFieldName(), true, referencedIdData, - revisionPropertyPath, originalIdPropertyName, REFERENCED_ENTITY_ALIAS, REFERENCED_ENTITY_ALIAS_DEF_AUD_STR); + /** + * Creates query restrictions used to retrieve only actual data. + */ + private void createValidDataRestrictions(GlobalConfiguration globalCfg, AuditStrategy auditStrategy, + MiddleIdData referencedIdData, QueryBuilder qb, Parameters rootParameters, + boolean inclusive) { + final String revisionPropertyPath = verEntCfg.getRevisionNumberPath(); + // (selecting e entities at revision :revision) + // --> based on auditStrategy (see above) + auditStrategy.addEntityAtRevisionRestriction( + globalCfg, qb, rootParameters, revisionPropertyPath, + verEntCfg.getRevisionEndFieldName(), true, referencedIdData, revisionPropertyPath, + verEntCfg.getOriginalIdPropName(), REFERENCED_ENTITY_ALIAS, REFERENCED_ENTITY_ALIAS_DEF_AUD_STR, + inclusive + ); + // e.revision_type != DEL + rootParameters.addWhereWithNamedParam( getRevisionTypePath(), false, "!=", DEL_REVISION_TYPE_PARAMETER ); + } - // e.revision_type != DEL - rootParameters.addWhereWithNamedParam(getRevisionTypePath(), false, "!=", DEL_REVISION_TYPE_PARAMETER); - - StringBuilder sb = new StringBuilder(); - qb.build(sb, Collections.emptyMap()); - queryString = sb.toString(); - } + /** + * Create query restrictions used to retrieve actual data and deletions that took place at exactly given revision. + */ + private void createValidAndRemovedDataRestrictions(GlobalConfiguration globalCfg, AuditStrategy auditStrategy, + MiddleIdData referencedIdData, QueryBuilder remQb) { + final Parameters disjoint = remQb.getRootParameters().addSubParameters( "or" ); + final Parameters valid = disjoint.addSubParameters( "and" ); // Restrictions to match all valid rows. + final Parameters removed = disjoint.addSubParameters( "and" ); // Restrictions to match all rows deleted at exactly given revision. + // Excluding current revision, because we need to match data valid at the previous one. + createValidDataRestrictions( globalCfg, auditStrategy, referencedIdData, remQb, valid, false ); + // e.revision = :revision + removed.addWhereWithNamedParam( verEntCfg.getRevisionNumberPath(), false, "=", REVISION_PARAMETER ); + // e.revision_type = DEL + removed.addWhereWithNamedParam( getRevisionTypePath(), false, "=", DEL_REVISION_TYPE_PARAMETER ); + } @Override protected String getQueryString() { return queryString; } + + @Override + protected String getQueryRemovedString() { + return queryRemovedString; + } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/OneEntityQueryGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/OneEntityQueryGenerator.java index 45d8e47728..74ab5b7491 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/OneEntityQueryGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/OneEntityQueryGenerator.java @@ -23,18 +23,12 @@ */ package org.hibernate.envers.entities.mapper.relation.query; -import java.util.Collections; - -import org.hibernate.Query; -import org.hibernate.envers.RevisionType; import org.hibernate.envers.configuration.AuditEntitiesConfiguration; -import org.hibernate.envers.entities.mapper.id.QueryParameterData; import org.hibernate.envers.entities.mapper.relation.MiddleComponentData; import org.hibernate.envers.entities.mapper.relation.MiddleIdData; -import org.hibernate.envers.reader.AuditReaderImplementor; -import org.hibernate.envers.strategy.AuditStrategy; import org.hibernate.envers.tools.query.Parameters; import org.hibernate.envers.tools.query.QueryBuilder; +import org.hibernate.envers.strategy.AuditStrategy; import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants.DEL_REVISION_TYPE_PARAMETER; import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants.MIDDLE_ENTITY_ALIAS; @@ -42,67 +36,106 @@ import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants /** * Selects data from a relation middle-table only. + * * @author Adam Warski (adam at warski dot org) + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) */ public final class OneEntityQueryGenerator extends AbstractRelationQueryGenerator { - private final String queryString; + private final String queryString; + private final String queryRemovedString; - public OneEntityQueryGenerator(AuditEntitiesConfiguration verEntCfg, - AuditStrategy auditStrategy, - String versionsMiddleEntityName, - MiddleIdData referencingIdData, - boolean revisionTypeInId, - MiddleComponentData... componentDatas) { + public OneEntityQueryGenerator(AuditEntitiesConfiguration verEntCfg, AuditStrategy auditStrategy, + String versionsMiddleEntityName, MiddleIdData referencingIdData, + boolean revisionTypeInId, MiddleComponentData... componentData) { super( verEntCfg, referencingIdData, revisionTypeInId ); - /* - * The query that we need to create: - * SELECT ee FROM middleEntity ee WHERE - * (only entities referenced by the association; id_ref_ing = id of the referencing entity) - * ee.originalId.id_ref_ing = :id_ref_ing AND - * - * (the association at revision :revision) - * --> for DefaultAuditStrategy: - * ee.revision = (SELECT max(ee2.revision) FROM middleEntity ee2 - * WHERE ee2.revision <= :revision AND ee2.originalId.* = ee.originalId.*) - * - * --> for ValidityAuditStrategy: - * ee.revision <= :revision and (ee.endRevision > :revision or ee.endRevision is null) - * - * AND - * - * (only non-deleted entities and associations) - * ee.revision_type != DEL - */ - String revisionPropertyPath = verEntCfg.getRevisionNumberPath(); - String originalIdPropertyName = verEntCfg.getOriginalIdPropName(); + /* + * The valid query that we need to create: + * SELECT ee FROM middleEntity ee WHERE + * (only entities referenced by the association; id_ref_ing = id of the referencing entity) + * ee.originalId.id_ref_ing = :id_ref_ing AND + * + * (the association at revision :revision) + * --> for DefaultAuditStrategy: + * ee.revision = (SELECT max(ee2.revision) FROM middleEntity ee2 + * WHERE ee2.revision <= :revision AND ee2.originalId.* = ee.originalId.*) + * + * --> for ValidityAuditStrategy: + * ee.revision <= :revision and (ee.endRevision > :revision or ee.endRevision is null) + * + * AND + * + * (only non-deleted entities and associations) + * ee.revision_type != DEL + */ + final QueryBuilder commonPart = commonQueryPart( versionsMiddleEntityName ); + final QueryBuilder validQuery = commonPart.deepCopy(); + final QueryBuilder removedQuery = commonPart.deepCopy(); + createValidDataRestrictions( + auditStrategy, versionsMiddleEntityName, validQuery, validQuery.getRootParameters(), true, componentData + ); + createValidAndRemovedDataRestrictions( auditStrategy, versionsMiddleEntityName, removedQuery, componentData ); - // SELECT ee FROM middleEntity ee - QueryBuilder qb = new QueryBuilder(versionsMiddleEntityName, MIDDLE_ENTITY_ALIAS); - qb.addProjection(null, MIDDLE_ENTITY_ALIAS, false, false); - // WHERE - Parameters rootParameters = qb.getRootParameters(); - // ee.originalId.id_ref_ing = :id_ref_ing - referencingIdData.getPrefixedMapper().addNamedIdEqualsToQuery(rootParameters, originalIdPropertyName, true); - - String eeOriginalIdPropertyPath = MIDDLE_ENTITY_ALIAS + "." + originalIdPropertyName; + queryString = queryToString( validQuery ); + queryRemovedString = queryToString( removedQuery ); + } - // (with ee association at revision :revision) - // --> based on auditStrategy (see above) - auditStrategy.addAssociationAtRevisionRestriction(qb, revisionPropertyPath, - verEntCfg.getRevisionEndFieldName(), true, referencingIdData, versionsMiddleEntityName, - eeOriginalIdPropertyPath, revisionPropertyPath, originalIdPropertyName, MIDDLE_ENTITY_ALIAS, componentDatas); - - // ee.revision_type != DEL - rootParameters.addWhereWithNamedParam(getRevisionTypePath(), "!=", DEL_REVISION_TYPE_PARAMETER); + /** + * Compute common part for both queries. + */ + private QueryBuilder commonQueryPart(String versionsMiddleEntityName) { + // SELECT ee FROM middleEntity ee + final QueryBuilder qb = new QueryBuilder( versionsMiddleEntityName, MIDDLE_ENTITY_ALIAS ); + qb.addProjection( null, MIDDLE_ENTITY_ALIAS, false, false ); + // WHERE + // ee.originalId.id_ref_ing = :id_ref_ing + referencingIdData.getPrefixedMapper().addNamedIdEqualsToQuery( qb.getRootParameters(), verEntCfg.getOriginalIdPropName(), true ); + return qb; + } - StringBuilder sb = new StringBuilder(); - qb.build(sb, Collections.emptyMap()); - queryString = sb.toString(); - } + /** + * Creates query restrictions used to retrieve only actual data. + */ + private void createValidDataRestrictions(AuditStrategy auditStrategy, String versionsMiddleEntityName, + QueryBuilder qb, Parameters rootParameters, boolean inclusive, + MiddleComponentData... componentData) { + final String revisionPropertyPath = verEntCfg.getRevisionNumberPath(); + final String originalIdPropertyName = verEntCfg.getOriginalIdPropName(); + final String eeOriginalIdPropertyPath = MIDDLE_ENTITY_ALIAS + "." + originalIdPropertyName; + // (with ee association at revision :revision) + // --> based on auditStrategy (see above) + auditStrategy.addAssociationAtRevisionRestriction( + qb, rootParameters, revisionPropertyPath, verEntCfg.getRevisionEndFieldName(), true, + referencingIdData, versionsMiddleEntityName, eeOriginalIdPropertyPath, revisionPropertyPath, + originalIdPropertyName, MIDDLE_ENTITY_ALIAS, inclusive, componentData + ); + // ee.revision_type != DEL + rootParameters.addWhereWithNamedParam( getRevisionTypePath(), "!=", DEL_REVISION_TYPE_PARAMETER ); + } + + /** + * Create query restrictions used to retrieve actual data and deletions that took place at exactly given revision. + */ + private void createValidAndRemovedDataRestrictions(AuditStrategy auditStrategy, String versionsMiddleEntityName, + QueryBuilder remQb, MiddleComponentData... componentData) { + final Parameters disjoint = remQb.getRootParameters().addSubParameters( "or" ); + final Parameters valid = disjoint.addSubParameters( "and" ); // Restrictions to match all valid rows. + final Parameters removed = disjoint.addSubParameters( "and" ); // Restrictions to match all rows deleted at exactly given revision. + // Excluding current revision, because we need to match data valid at the previous one. + createValidDataRestrictions( auditStrategy, versionsMiddleEntityName, remQb, valid, false, componentData ); + // ee.revision = :revision + removed.addWhereWithNamedParam( verEntCfg.getRevisionNumberPath(), "=", REVISION_PARAMETER ); + // ee.revision_type = DEL + removed.addWhereWithNamedParam( getRevisionTypePath(), "=", DEL_REVISION_TYPE_PARAMETER ); + } @Override protected String getQueryString() { return queryString; } + + @Override + protected String getQueryRemovedString() { + return queryRemovedString; + } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/RelationQueryGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/RelationQueryGenerator.java index 9957454b22..454da9ec54 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/RelationQueryGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/RelationQueryGenerator.java @@ -34,5 +34,5 @@ import org.hibernate.envers.reader.AuditReaderImplementor; * @author Adam Warski (adam at warski dot org) */ public interface RelationQueryGenerator { - Query getQuery(AuditReaderImplementor versionsReader, Object primaryKey, Number revision); + Query getQuery(AuditReaderImplementor versionsReader, Object primaryKey, Number revision, boolean removed); } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/ThreeEntityQueryGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/ThreeEntityQueryGenerator.java index 782d12cea3..f46c49023b 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/ThreeEntityQueryGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/ThreeEntityQueryGenerator.java @@ -23,19 +23,13 @@ */ package org.hibernate.envers.entities.mapper.relation.query; -import java.util.Collections; - -import org.hibernate.Query; -import org.hibernate.envers.RevisionType; import org.hibernate.envers.configuration.AuditEntitiesConfiguration; import org.hibernate.envers.configuration.GlobalConfiguration; -import org.hibernate.envers.entities.mapper.id.QueryParameterData; import org.hibernate.envers.entities.mapper.relation.MiddleComponentData; import org.hibernate.envers.entities.mapper.relation.MiddleIdData; -import org.hibernate.envers.reader.AuditReaderImplementor; -import org.hibernate.envers.strategy.AuditStrategy; import org.hibernate.envers.tools.query.Parameters; import org.hibernate.envers.tools.query.QueryBuilder; +import org.hibernate.envers.strategy.AuditStrategy; import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants.DEL_REVISION_TYPE_PARAMETER; import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants.INDEX_ENTITY_ALIAS; @@ -47,133 +41,188 @@ import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants /** * Selects data from a relation middle-table and a two related versions entity. + * * @author Adam Warski (adam at warski dot org) + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) */ public final class ThreeEntityQueryGenerator extends AbstractRelationQueryGenerator { - private final String queryString; + private final String queryString; + private final String queryRemovedString; - public ThreeEntityQueryGenerator(GlobalConfiguration globalCfg, - AuditEntitiesConfiguration verEntCfg, - AuditStrategy auditStrategy, - String versionsMiddleEntityName, - MiddleIdData referencingIdData, - MiddleIdData referencedIdData, - MiddleIdData indexIdData, - boolean revisionTypeInId, - MiddleComponentData... componentDatas) { + public ThreeEntityQueryGenerator(GlobalConfiguration globalCfg, AuditEntitiesConfiguration verEntCfg, + AuditStrategy auditStrategy, String versionsMiddleEntityName, + MiddleIdData referencingIdData, MiddleIdData referencedIdData, + MiddleIdData indexIdData, boolean revisionTypeInId, + MiddleComponentData... componentData) { super( verEntCfg, referencingIdData, revisionTypeInId ); - /* - * The query that we need to create: - * SELECT new list(ee, e, f) FROM versionsReferencedEntity e, versionsIndexEntity f, middleEntity ee - * WHERE - * (entities referenced by the middle table; id_ref_ed = id of the referenced entity) - * ee.id_ref_ed = e.id_ref_ed AND - * (entities referenced by the middle table; id_ref_ind = id of the index entity) - * ee.id_ref_ind = f.id_ref_ind AND - * (only entities referenced by the association; id_ref_ing = id of the referencing entity) - * ee.id_ref_ing = :id_ref_ing AND - * (selecting e entities at revision :revision) - * --> for DefaultAuditStrategy: - * e.revision = (SELECT max(e2.revision) FROM versionsReferencedEntity e2 - * WHERE e2.revision <= :revision AND e2.id = e.id) - * - * --> for ValidityAuditStrategy: - * e.revision <= :revision and (e.endRevision > :revision or e.endRevision is null) - * - * AND - * - * (selecting f entities at revision :revision) - * --> for DefaultAuditStrategy: - * f.revision = (SELECT max(f2.revision) FROM versionsIndexEntity f2 - * WHERE f2.revision <= :revision AND f2.id_ref_ed = f.id_ref_ed) - * - * --> for ValidityAuditStrategy: - * f.revision <= :revision and (f.endRevision > :revision or f.endRevision is null) - * - * AND - * - * (the association at revision :revision) - * --> for DefaultAuditStrategy: - * ee.revision = (SELECT max(ee2.revision) FROM middleEntity ee2 - * WHERE ee2.revision <= :revision AND ee2.originalId.* = ee.originalId.*) - * - * --> for ValidityAuditStrategy: - * ee.revision <= :revision and (ee.endRevision > :revision or ee.endRevision is null) - * - and ( - strtestent1_.REVEND>? - or strtestent1_.REVEND is null - ) - and ( - strtestent1_.REVEND>? - or strtestent1_.REVEND is null - ) - and ( - ternarymap0_.REVEND>? - or ternarymap0_.REVEND is null - ) - * - * - * - * (only non-deleted entities and associations) - * ee.revision_type != DEL AND - * e.revision_type != DEL AND - * f.revision_type != DEL - */ - String revisionPropertyPath = verEntCfg.getRevisionNumberPath(); - String originalIdPropertyName = verEntCfg.getOriginalIdPropName(); - String eeOriginalIdPropertyPath = MIDDLE_ENTITY_ALIAS + "." + originalIdPropertyName; + /* + * The valid query that we need to create: + * SELECT new list(ee, e, f) FROM versionsReferencedEntity e, versionsIndexEntity f, middleEntity ee + * WHERE + * (entities referenced by the middle table; id_ref_ed = id of the referenced entity) + * ee.id_ref_ed = e.id_ref_ed AND + * (entities referenced by the middle table; id_ref_ind = id of the index entity) + * ee.id_ref_ind = f.id_ref_ind AND + * (only entities referenced by the association; id_ref_ing = id of the referencing entity) + * ee.id_ref_ing = :id_ref_ing AND + * (selecting e entities at revision :revision) + * --> for DefaultAuditStrategy: + * e.revision = (SELECT max(e2.revision) FROM versionsReferencedEntity e2 + * WHERE e2.revision <= :revision AND e2.id = e.id) + * + * --> for ValidityAuditStrategy: + * e.revision <= :revision and (e.endRevision > :revision or e.endRevision is null) + * + * AND + * + * (selecting f entities at revision :revision) + * --> for DefaultAuditStrategy: + * f.revision = (SELECT max(f2.revision) FROM versionsIndexEntity f2 + * WHERE f2.revision <= :revision AND f2.id_ref_ed = f.id_ref_ed) + * + * --> for ValidityAuditStrategy: + * f.revision <= :revision and (f.endRevision > :revision or f.endRevision is null) + * + * AND + * + * (the association at revision :revision) + * --> for DefaultAuditStrategy: + * ee.revision = (SELECT max(ee2.revision) FROM middleEntity ee2 + * WHERE ee2.revision <= :revision AND ee2.originalId.* = ee.originalId.*) + * + * --> for ValidityAuditStrategy: + * ee.revision <= :revision and (ee.endRevision > :revision or ee.endRevision is null) + * and ( strtestent1_.REVEND>? or strtestent1_.REVEND is null ) + * and ( strtestent1_.REVEND>? or strtestent1_.REVEND is null ) + * and ( ternarymap0_.REVEND>? or ternarymap0_.REVEND is null ) + * + * (only non-deleted entities and associations) + * ee.revision_type != DEL AND + * e.revision_type != DEL AND + * f.revision_type != DEL + */ + final QueryBuilder commonPart = commonQueryPart( referencedIdData, indexIdData, versionsMiddleEntityName, verEntCfg.getOriginalIdPropName() ); + final QueryBuilder validQuery = commonPart.deepCopy(); + final QueryBuilder removedQuery = commonPart.deepCopy(); + createValidDataRestrictions( + globalCfg, auditStrategy, referencedIdData, versionsMiddleEntityName, validQuery, + validQuery.getRootParameters(), true, componentData + ); + createValidAndRemovedDataRestrictions( + globalCfg, auditStrategy, referencedIdData, versionsMiddleEntityName, removedQuery, componentData + ); - // SELECT new list(ee) FROM middleEntity ee - QueryBuilder qb = new QueryBuilder(versionsMiddleEntityName, MIDDLE_ENTITY_ALIAS); - qb.addFrom(referencedIdData.getAuditEntityName(), REFERENCED_ENTITY_ALIAS); - qb.addFrom(indexIdData.getAuditEntityName(), INDEX_ENTITY_ALIAS); - qb.addProjection("new list", MIDDLE_ENTITY_ALIAS + ", " + REFERENCED_ENTITY_ALIAS + ", " + INDEX_ENTITY_ALIAS, false, false); - // WHERE - Parameters rootParameters = qb.getRootParameters(); - // ee.id_ref_ed = e.id_ref_ed - referencedIdData.getPrefixedMapper().addIdsEqualToQuery(rootParameters, eeOriginalIdPropertyPath, - referencedIdData.getOriginalMapper(), REFERENCED_ENTITY_ALIAS + "." + originalIdPropertyName); - // ee.id_ref_ind = f.id_ref_ind - indexIdData.getPrefixedMapper().addIdsEqualToQuery(rootParameters, eeOriginalIdPropertyPath, - indexIdData.getOriginalMapper(), INDEX_ENTITY_ALIAS + "." + originalIdPropertyName); - // ee.originalId.id_ref_ing = :id_ref_ing - referencingIdData.getPrefixedMapper().addNamedIdEqualsToQuery(rootParameters, originalIdPropertyName, true); + queryString = queryToString( validQuery ); + queryRemovedString = queryToString( removedQuery ); + } - // (selecting e entities at revision :revision) - // --> based on auditStrategy (see above) - auditStrategy.addEntityAtRevisionRestriction(globalCfg, qb, REFERENCED_ENTITY_ALIAS + "." + revisionPropertyPath, - REFERENCED_ENTITY_ALIAS + "." + verEntCfg.getRevisionEndFieldName(), false, - referencedIdData, revisionPropertyPath, originalIdPropertyName, REFERENCED_ENTITY_ALIAS, REFERENCED_ENTITY_ALIAS_DEF_AUD_STR); - - // (selecting f entities at revision :revision) - // --> based on auditStrategy (see above) - auditStrategy.addEntityAtRevisionRestriction(globalCfg, qb, REFERENCED_ENTITY_ALIAS + "." + revisionPropertyPath, - REFERENCED_ENTITY_ALIAS + "." + verEntCfg.getRevisionEndFieldName(), false, - referencedIdData, revisionPropertyPath, originalIdPropertyName, INDEX_ENTITY_ALIAS, INDEX_ENTITY_ALIAS_DEF_AUD_STR); + /** + * Compute common part for both queries. + */ + private QueryBuilder commonQueryPart(MiddleIdData referencedIdData, MiddleIdData indexIdData, + String versionsMiddleEntityName, String originalIdPropertyName) { + final String eeOriginalIdPropertyPath = MIDDLE_ENTITY_ALIAS + "." + originalIdPropertyName; + // SELECT new list(ee) FROM middleEntity ee + final QueryBuilder qb = new QueryBuilder( versionsMiddleEntityName, MIDDLE_ENTITY_ALIAS ); + qb.addFrom( referencedIdData.getAuditEntityName(), REFERENCED_ENTITY_ALIAS ); + qb.addFrom( indexIdData.getAuditEntityName(), INDEX_ENTITY_ALIAS ); + qb.addProjection( + "new list", MIDDLE_ENTITY_ALIAS + ", " + REFERENCED_ENTITY_ALIAS + ", " + INDEX_ENTITY_ALIAS, + false, false + ); + // WHERE + final Parameters rootParameters = qb.getRootParameters(); + // ee.id_ref_ed = e.id_ref_ed + referencedIdData.getPrefixedMapper().addIdsEqualToQuery( + rootParameters, eeOriginalIdPropertyPath, referencedIdData.getOriginalMapper(), + REFERENCED_ENTITY_ALIAS + "." + originalIdPropertyName + ); + // ee.id_ref_ind = f.id_ref_ind + indexIdData.getPrefixedMapper().addIdsEqualToQuery( + rootParameters, eeOriginalIdPropertyPath, indexIdData.getOriginalMapper(), + INDEX_ENTITY_ALIAS + "." + originalIdPropertyName + ); + // ee.originalId.id_ref_ing = :id_ref_ing + referencingIdData.getPrefixedMapper().addNamedIdEqualsToQuery( rootParameters, originalIdPropertyName, true ); + return qb; + } - // (with ee association at revision :revision) - // --> based on auditStrategy (see above) - auditStrategy.addAssociationAtRevisionRestriction(qb, revisionPropertyPath, - verEntCfg.getRevisionEndFieldName(), true, referencingIdData, versionsMiddleEntityName, - eeOriginalIdPropertyPath, revisionPropertyPath, originalIdPropertyName, MIDDLE_ENTITY_ALIAS, componentDatas); + /** + * Creates query restrictions used to retrieve only actual data. + */ + private void createValidDataRestrictions(GlobalConfiguration globalCfg, AuditStrategy auditStrategy, + MiddleIdData referencedIdData, String versionsMiddleEntityName, QueryBuilder qb, + Parameters rootParameters, boolean inclusive, MiddleComponentData... componentData) { + final String revisionPropertyPath = verEntCfg.getRevisionNumberPath(); + final String originalIdPropertyName = verEntCfg.getOriginalIdPropName(); + final String eeOriginalIdPropertyPath = MIDDLE_ENTITY_ALIAS + "." + originalIdPropertyName; + final String revisionTypePropName = getRevisionTypePath(); + // (selecting e entities at revision :revision) + // --> based on auditStrategy (see above) + auditStrategy.addEntityAtRevisionRestriction( + globalCfg, qb, rootParameters, REFERENCED_ENTITY_ALIAS + "." + revisionPropertyPath, + REFERENCED_ENTITY_ALIAS + "." + verEntCfg.getRevisionEndFieldName(), false, referencedIdData, revisionPropertyPath, + originalIdPropertyName, REFERENCED_ENTITY_ALIAS, REFERENCED_ENTITY_ALIAS_DEF_AUD_STR, inclusive + ); + // (selecting f entities at revision :revision) + // --> based on auditStrategy (see above) + auditStrategy.addEntityAtRevisionRestriction( + globalCfg, qb, rootParameters, REFERENCED_ENTITY_ALIAS + "." + revisionPropertyPath, + REFERENCED_ENTITY_ALIAS + "." + verEntCfg.getRevisionEndFieldName(), false, referencedIdData, revisionPropertyPath, + originalIdPropertyName, INDEX_ENTITY_ALIAS, INDEX_ENTITY_ALIAS_DEF_AUD_STR, inclusive + ); + // (with ee association at revision :revision) + // --> based on auditStrategy (see above) + auditStrategy.addAssociationAtRevisionRestriction( + qb, rootParameters, revisionPropertyPath, verEntCfg.getRevisionEndFieldName(), true, + referencingIdData, versionsMiddleEntityName, eeOriginalIdPropertyPath, revisionPropertyPath, + originalIdPropertyName, MIDDLE_ENTITY_ALIAS, inclusive, componentData + ); + // ee.revision_type != DEL + rootParameters.addWhereWithNamedParam( revisionTypePropName, "!=", DEL_REVISION_TYPE_PARAMETER ); + // e.revision_type != DEL + rootParameters.addWhereWithNamedParam( REFERENCED_ENTITY_ALIAS + "." + revisionTypePropName, false, "!=", DEL_REVISION_TYPE_PARAMETER ); + // f.revision_type != DEL + rootParameters.addWhereWithNamedParam( INDEX_ENTITY_ALIAS + "." + revisionTypePropName, false, "!=", DEL_REVISION_TYPE_PARAMETER ); + } - // ee.revision_type != DEL - String revisionTypePropName = getRevisionTypePath(); - rootParameters.addWhereWithNamedParam(revisionTypePropName, "!=", DEL_REVISION_TYPE_PARAMETER); - // e.revision_type != DEL - rootParameters.addWhereWithNamedParam(REFERENCED_ENTITY_ALIAS + "." + revisionTypePropName, false, "!=", DEL_REVISION_TYPE_PARAMETER); - // f.revision_type != DEL - rootParameters.addWhereWithNamedParam(INDEX_ENTITY_ALIAS + "." + revisionTypePropName, false, "!=", DEL_REVISION_TYPE_PARAMETER); - - StringBuilder sb = new StringBuilder(); - qb.build(sb, Collections.emptyMap()); - queryString = sb.toString(); - } + /** + * Create query restrictions used to retrieve actual data and deletions that took place at exactly given revision. + */ + private void createValidAndRemovedDataRestrictions(GlobalConfiguration globalCfg, AuditStrategy auditStrategy, + MiddleIdData referencedIdData, String versionsMiddleEntityName, + QueryBuilder remQb, MiddleComponentData... componentData) { + final Parameters disjoint = remQb.getRootParameters().addSubParameters( "or" ); + final Parameters valid = disjoint.addSubParameters( "and" ); // Restrictions to match all valid rows. + final Parameters removed = disjoint.addSubParameters( "and" ); // Restrictions to match all rows deleted at exactly given revision. + final String revisionPropertyPath = verEntCfg.getRevisionNumberPath(); + final String revisionTypePropName = getRevisionTypePath(); + // Excluding current revision, because we need to match data valid at the previous one. + createValidDataRestrictions( + globalCfg, auditStrategy, referencedIdData, versionsMiddleEntityName, remQb, valid, false, componentData + ); + // ee.revision = :revision + removed.addWhereWithNamedParam( revisionPropertyPath, "=", REVISION_PARAMETER ); + // e.revision = :revision + removed.addWhereWithNamedParam( REFERENCED_ENTITY_ALIAS + "." + revisionPropertyPath, false, "=", REVISION_PARAMETER ); + // f.revision = :revision + removed.addWhereWithNamedParam( INDEX_ENTITY_ALIAS + "." + revisionPropertyPath, false, "=", REVISION_PARAMETER ); + // ee.revision_type = DEL + removed.addWhereWithNamedParam( revisionTypePropName, "=", DEL_REVISION_TYPE_PARAMETER ); + // e.revision_type = DEL + removed.addWhereWithNamedParam( REFERENCED_ENTITY_ALIAS + "." + revisionTypePropName, false, "=", DEL_REVISION_TYPE_PARAMETER ); + // f.revision_type = DEL + removed.addWhereWithNamedParam( INDEX_ENTITY_ALIAS + "." + revisionTypePropName, false, "=", DEL_REVISION_TYPE_PARAMETER ); + } @Override protected String getQueryString() { return queryString; } -} + + @Override + protected String getQueryRemovedString() { + return queryRemovedString; + } +} \ No newline at end of file diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/TwoEntityOneAuditedQueryGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/TwoEntityOneAuditedQueryGenerator.java index d5402cd5cd..2c3d13a653 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/TwoEntityOneAuditedQueryGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/TwoEntityOneAuditedQueryGenerator.java @@ -23,18 +23,12 @@ */ package org.hibernate.envers.entities.mapper.relation.query; -import java.util.Collections; - -import org.hibernate.Query; -import org.hibernate.envers.RevisionType; import org.hibernate.envers.configuration.AuditEntitiesConfiguration; -import org.hibernate.envers.entities.mapper.id.QueryParameterData; import org.hibernate.envers.entities.mapper.relation.MiddleComponentData; import org.hibernate.envers.entities.mapper.relation.MiddleIdData; -import org.hibernate.envers.reader.AuditReaderImplementor; -import org.hibernate.envers.strategy.AuditStrategy; import org.hibernate.envers.tools.query.Parameters; import org.hibernate.envers.tools.query.QueryBuilder; +import org.hibernate.envers.strategy.AuditStrategy; import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants.DEL_REVISION_TYPE_PARAMETER; import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants.MIDDLE_ENTITY_ALIAS; @@ -43,74 +37,116 @@ import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants /** * Selects data from a relation middle-table and a related non-audited entity. + * * @author Adam Warski (adam at warski dot org) + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) */ public final class TwoEntityOneAuditedQueryGenerator extends AbstractRelationQueryGenerator { - private final String queryString; + private final String queryString; + private final String queryRemovedString; - public TwoEntityOneAuditedQueryGenerator(AuditEntitiesConfiguration verEntCfg, AuditStrategy auditStrategy, - String versionsMiddleEntityName, - MiddleIdData referencingIdData, - MiddleIdData referencedIdData, - boolean revisionTypeInId, - MiddleComponentData... componentDatas) { + public TwoEntityOneAuditedQueryGenerator(AuditEntitiesConfiguration verEntCfg, AuditStrategy auditStrategy, + String versionsMiddleEntityName, MiddleIdData referencingIdData, + MiddleIdData referencedIdData, boolean revisionTypeInId, + MiddleComponentData... componentData) { super( verEntCfg, referencingIdData, revisionTypeInId ); - /* - * The query that we need to create: - * SELECT new list(ee, e) FROM referencedEntity e, middleEntity ee - * WHERE - * (entities referenced by the middle table; id_ref_ed = id of the referenced entity) - * ee.id_ref_ed = e.id_ref_ed AND - * (only entities referenced by the association; id_ref_ing = id of the referencing entity) - * ee.id_ref_ing = :id_ref_ing AND - * - * (the association at revision :revision) - * --> for DefaultAuditStrategy: - * ee.revision = (SELECT max(ee2.revision) FROM middleEntity ee2 - * WHERE ee2.revision <= :revision AND ee2.originalId.* = ee.originalId.*) - * - * --> for ValidityAuditStrategy: - * ee.revision <= :revision and (ee.endRevision > :revision or ee.endRevision is null) - * - * AND - * - * (only non-deleted entities and associations) - * ee.revision_type != DEL - */ - String revisionPropertyPath = verEntCfg.getRevisionNumberPath(); - String originalIdPropertyName = verEntCfg.getOriginalIdPropName(); + /* + * The valid query that we need to create: + * SELECT new list(ee, e) FROM referencedEntity e, middleEntity ee + * WHERE + * (entities referenced by the middle table; id_ref_ed = id of the referenced entity) + * ee.id_ref_ed = e.id_ref_ed AND + * (only entities referenced by the association; id_ref_ing = id of the referencing entity) + * ee.id_ref_ing = :id_ref_ing AND + * + * (the association at revision :revision) + * --> for DefaultAuditStrategy: + * ee.revision = (SELECT max(ee2.revision) FROM middleEntity ee2 + * WHERE ee2.revision <= :revision AND ee2.originalId.* = ee.originalId.*) + * + * --> for ValidityAuditStrategy: + * ee.revision <= :revision and (ee.endRevision > :revision or ee.endRevision is null) + * + * AND + * + * (only non-deleted entities and associations) + * ee.revision_type != DEL + */ + final QueryBuilder commonPart = commonQueryPart( referencedIdData, versionsMiddleEntityName, verEntCfg.getOriginalIdPropName() ); + final QueryBuilder validQuery = commonPart.deepCopy(); + final QueryBuilder removedQuery = commonPart.deepCopy(); + createValidDataRestrictions( + auditStrategy, versionsMiddleEntityName, validQuery, validQuery.getRootParameters(), componentData + ); + createValidAndRemovedDataRestrictions( auditStrategy, versionsMiddleEntityName, removedQuery, componentData ); - String eeOriginalIdPropertyPath = MIDDLE_ENTITY_ALIAS + "." + originalIdPropertyName; + queryString = queryToString( validQuery ); + queryRemovedString = queryToString( removedQuery ); + } - // SELECT new list(ee) FROM middleEntity ee - QueryBuilder qb = new QueryBuilder(versionsMiddleEntityName, MIDDLE_ENTITY_ALIAS); - qb.addFrom(referencedIdData.getEntityName(), REFERENCED_ENTITY_ALIAS); - qb.addProjection("new list", MIDDLE_ENTITY_ALIAS + ", " + REFERENCED_ENTITY_ALIAS, false, false); - // WHERE - Parameters rootParameters = qb.getRootParameters(); - // ee.id_ref_ed = e.id_ref_ed - referencedIdData.getPrefixedMapper().addIdsEqualToQuery(rootParameters, eeOriginalIdPropertyPath, - referencedIdData.getOriginalMapper(), REFERENCED_ENTITY_ALIAS); - // ee.originalId.id_ref_ing = :id_ref_ing - referencingIdData.getPrefixedMapper().addNamedIdEqualsToQuery(rootParameters, originalIdPropertyName, true); + /** + * Compute common part for both queries. + */ + private QueryBuilder commonQueryPart(MiddleIdData referencedIdData, String versionsMiddleEntityName, + String originalIdPropertyName) { + final String eeOriginalIdPropertyPath = MIDDLE_ENTITY_ALIAS + "." + originalIdPropertyName; + // SELECT new list(ee) FROM middleEntity ee + final QueryBuilder qb = new QueryBuilder( versionsMiddleEntityName, MIDDLE_ENTITY_ALIAS ); + qb.addFrom( referencedIdData.getEntityName(), REFERENCED_ENTITY_ALIAS ); + qb.addProjection( "new list", MIDDLE_ENTITY_ALIAS + ", " + REFERENCED_ENTITY_ALIAS, false, false ); + // WHERE + final Parameters rootParameters = qb.getRootParameters(); + // ee.id_ref_ed = e.id_ref_ed + referencedIdData.getPrefixedMapper().addIdsEqualToQuery( + rootParameters, eeOriginalIdPropertyPath, referencedIdData.getOriginalMapper(), REFERENCED_ENTITY_ALIAS + ); + // ee.originalId.id_ref_ing = :id_ref_ing + referencingIdData.getPrefixedMapper().addNamedIdEqualsToQuery( rootParameters, originalIdPropertyName, true ); + return qb; + } - // (with ee association at revision :revision) - // --> based on auditStrategy (see above) - auditStrategy.addAssociationAtRevisionRestriction(qb, revisionPropertyPath, - verEntCfg.getRevisionEndFieldName(), true,referencingIdData, versionsMiddleEntityName, - eeOriginalIdPropertyPath, revisionPropertyPath, originalIdPropertyName, MIDDLE_ENTITY_ALIAS, componentDatas); + /** + * Creates query restrictions used to retrieve only actual data. + */ + private void createValidDataRestrictions(AuditStrategy auditStrategy, String versionsMiddleEntityName, QueryBuilder qb, + Parameters rootParameters, MiddleComponentData... componentData) { + final String revisionPropertyPath = verEntCfg.getRevisionNumberPath(); + final String originalIdPropertyName = verEntCfg.getOriginalIdPropName(); + final String eeOriginalIdPropertyPath = MIDDLE_ENTITY_ALIAS + "." + originalIdPropertyName; + // (with ee association at revision :revision) + // --> based on auditStrategy (see above) + auditStrategy.addAssociationAtRevisionRestriction( + qb, rootParameters, revisionPropertyPath, verEntCfg.getRevisionEndFieldName(), true, + referencingIdData, versionsMiddleEntityName, eeOriginalIdPropertyPath, revisionPropertyPath, + originalIdPropertyName, MIDDLE_ENTITY_ALIAS, true, componentData + ); + // ee.revision_type != DEL + rootParameters.addWhereWithNamedParam( getRevisionTypePath(), "!=", DEL_REVISION_TYPE_PARAMETER ); + } - // ee.revision_type != DEL - rootParameters.addWhereWithNamedParam(getRevisionTypePath(), "!=", DEL_REVISION_TYPE_PARAMETER); - - StringBuilder sb = new StringBuilder(); - qb.build(sb, Collections.emptyMap()); - queryString = sb.toString(); - } + /** + * Create query restrictions used to retrieve actual data and deletions that took place at exactly given revision. + */ + private void createValidAndRemovedDataRestrictions(AuditStrategy auditStrategy, String versionsMiddleEntityName, + QueryBuilder remQb, MiddleComponentData... componentData) { + final Parameters disjoint = remQb.getRootParameters().addSubParameters( "or" ); + final Parameters valid = disjoint.addSubParameters( "and" ); // Restrictions to match all valid rows. + final Parameters removed = disjoint.addSubParameters( "and" ); // Restrictions to match all rows deleted at exactly given revision. + createValidDataRestrictions( auditStrategy, versionsMiddleEntityName, remQb, valid, componentData ); + // ee.revision = :revision + removed.addWhereWithNamedParam( verEntCfg.getRevisionNumberPath(), "=", REVISION_PARAMETER ); + // ee.revision_type = DEL + removed.addWhereWithNamedParam( getRevisionTypePath(), "=", DEL_REVISION_TYPE_PARAMETER ); + } @Override protected String getQueryString() { return queryString; } -} + + @Override + protected String getQueryRemovedString() { + return queryRemovedString; + } +} \ No newline at end of file diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/TwoEntityQueryGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/TwoEntityQueryGenerator.java index 42642bd7c6..8f28c2da6d 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/TwoEntityQueryGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/query/TwoEntityQueryGenerator.java @@ -23,19 +23,13 @@ */ package org.hibernate.envers.entities.mapper.relation.query; -import java.util.Collections; - -import org.hibernate.Query; -import org.hibernate.envers.RevisionType; import org.hibernate.envers.configuration.AuditEntitiesConfiguration; import org.hibernate.envers.configuration.GlobalConfiguration; -import org.hibernate.envers.entities.mapper.id.QueryParameterData; import org.hibernate.envers.entities.mapper.relation.MiddleComponentData; import org.hibernate.envers.entities.mapper.relation.MiddleIdData; -import org.hibernate.envers.reader.AuditReaderImplementor; -import org.hibernate.envers.strategy.AuditStrategy; import org.hibernate.envers.tools.query.Parameters; import org.hibernate.envers.tools.query.QueryBuilder; +import org.hibernate.envers.strategy.AuditStrategy; import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants.DEL_REVISION_TYPE_PARAMETER; import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants.MIDDLE_ENTITY_ALIAS; @@ -45,94 +39,148 @@ import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants /** * Selects data from a relation middle-table and a related versions entity. + * * @author Adam Warski (adam at warski dot org) + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) */ public final class TwoEntityQueryGenerator extends AbstractRelationQueryGenerator { - private final String queryString; + private final String queryString; + private final String queryRemovedString; - public TwoEntityQueryGenerator(GlobalConfiguration globalCfg, - AuditEntitiesConfiguration verEntCfg, - AuditStrategy auditStrategy, - String versionsMiddleEntityName, - MiddleIdData referencingIdData, - MiddleIdData referencedIdData, - boolean revisionTypeInId, - MiddleComponentData... componentDatas) { + public TwoEntityQueryGenerator(GlobalConfiguration globalCfg, AuditEntitiesConfiguration verEntCfg, + AuditStrategy auditStrategy, String versionsMiddleEntityName, + MiddleIdData referencingIdData, MiddleIdData referencedIdData, + boolean revisionTypeInId, MiddleComponentData... componentData) { super( verEntCfg, referencingIdData, revisionTypeInId ); - /* - * The query that we need to create: - * SELECT new list(ee, e) FROM versionsReferencedEntity e, middleEntity ee - * WHERE - * (entities referenced by the middle table; id_ref_ed = id of the referenced entity) - * ee.id_ref_ed = e.id_ref_ed AND - * (only entities referenced by the association; id_ref_ing = id of the referencing entity) - * ee.id_ref_ing = :id_ref_ing AND - * - * (selecting e entities at revision :revision) - * --> for DefaultAuditStrategy: - * e.revision = (SELECT max(e2.revision) FROM versionsReferencedEntity e2 - * WHERE e2.revision <= :revision AND e2.id = e.id) - * - * --> for ValidityAuditStrategy: - * e.revision <= :revision and (e.endRevision > :revision or e.endRevision is null) - * - * AND - * - * (the association at revision :revision) - * --> for DefaultAuditStrategy: - * ee.revision = (SELECT max(ee2.revision) FROM middleEntity ee2 - * WHERE ee2.revision <= :revision AND ee2.originalId.* = ee.originalId.*) - * - * --> for ValidityAuditStrategy: - * ee.revision <= :revision and (ee.endRevision > :revision or ee.endRevision is null) - * - * (only non-deleted entities and associations) - * ee.revision_type != DEL AND - * e.revision_type != DEL - */ - String revisionPropertyPath = verEntCfg.getRevisionNumberPath(); - String originalIdPropertyName = verEntCfg.getOriginalIdPropName(); + /* + * The valid query that we need to create: + * SELECT new list(ee, e) FROM versionsReferencedEntity e, middleEntity ee + * WHERE + * (entities referenced by the middle table; id_ref_ed = id of the referenced entity) + * ee.id_ref_ed = e.id_ref_ed AND + * (only entities referenced by the association; id_ref_ing = id of the referencing entity) + * ee.id_ref_ing = :id_ref_ing AND + * + * (selecting e entities at revision :revision) + * --> for DefaultAuditStrategy: + * e.revision = (SELECT max(e2.revision) FROM versionsReferencedEntity e2 + * WHERE e2.revision <= :revision AND e2.id = e.id) + * + * --> for ValidityAuditStrategy: + * e.revision <= :revision and (e.endRevision > :revision or e.endRevision is null) + * + * AND + * + * (the association at revision :revision) + * --> for DefaultAuditStrategy: + * ee.revision = (SELECT max(ee2.revision) FROM middleEntity ee2 + * WHERE ee2.revision <= :revision AND ee2.originalId.* = ee.originalId.*) + * + * --> for ValidityAuditStrategy: + * ee.revision <= :revision and (ee.endRevision > :revision or ee.endRevision is null) + * + * (only non-deleted entities and associations) + * ee.revision_type != DEL AND + * e.revision_type != DEL + */ + final QueryBuilder commonPart = commonQueryPart( referencedIdData, versionsMiddleEntityName, verEntCfg.getOriginalIdPropName() ); + final QueryBuilder validQuery = commonPart.deepCopy(); + final QueryBuilder removedQuery = commonPart.deepCopy(); + createValidDataRestrictions( + globalCfg, auditStrategy, referencedIdData, versionsMiddleEntityName, validQuery, + validQuery.getRootParameters(), true, componentData + ); + createValidAndRemovedDataRestrictions( + globalCfg, auditStrategy, referencedIdData, versionsMiddleEntityName, removedQuery, componentData + ); - String eeOriginalIdPropertyPath = MIDDLE_ENTITY_ALIAS + "." + originalIdPropertyName; + queryString = queryToString( validQuery ); + queryRemovedString = queryToString( removedQuery ); + } - // SELECT new list(ee) FROM middleEntity ee - QueryBuilder qb = new QueryBuilder(versionsMiddleEntityName, MIDDLE_ENTITY_ALIAS); - qb.addFrom(referencedIdData.getAuditEntityName(), REFERENCED_ENTITY_ALIAS); - qb.addProjection("new list", MIDDLE_ENTITY_ALIAS + ", " + REFERENCED_ENTITY_ALIAS, false, false); - // WHERE - Parameters rootParameters = qb.getRootParameters(); - // ee.id_ref_ed = e.id_ref_ed - referencedIdData.getPrefixedMapper().addIdsEqualToQuery(rootParameters, eeOriginalIdPropertyPath, - referencedIdData.getOriginalMapper(), REFERENCED_ENTITY_ALIAS + "." + originalIdPropertyName); - // ee.originalId.id_ref_ing = :id_ref_ing - referencingIdData.getPrefixedMapper().addNamedIdEqualsToQuery(rootParameters, originalIdPropertyName, true); + /** + * Compute common part for both queries. + */ + private QueryBuilder commonQueryPart(MiddleIdData referencedIdData, String versionsMiddleEntityName, + String originalIdPropertyName) { + final String eeOriginalIdPropertyPath = MIDDLE_ENTITY_ALIAS + "." + originalIdPropertyName; + // SELECT new list(ee) FROM middleEntity ee + QueryBuilder qb = new QueryBuilder( versionsMiddleEntityName, MIDDLE_ENTITY_ALIAS ); + qb.addFrom( referencedIdData.getAuditEntityName(), REFERENCED_ENTITY_ALIAS ); + qb.addProjection( "new list", MIDDLE_ENTITY_ALIAS + ", " + REFERENCED_ENTITY_ALIAS, false, false ); + // WHERE + final Parameters rootParameters = qb.getRootParameters(); + // ee.id_ref_ed = e.id_ref_ed + referencedIdData.getPrefixedMapper().addIdsEqualToQuery( + rootParameters, eeOriginalIdPropertyPath, referencedIdData.getOriginalMapper(), + REFERENCED_ENTITY_ALIAS + "." + originalIdPropertyName + ); + // ee.originalId.id_ref_ing = :id_ref_ing + referencingIdData.getPrefixedMapper().addNamedIdEqualsToQuery( rootParameters, originalIdPropertyName, true ); + return qb; + } - // (selecting e entities at revision :revision) - // --> based on auditStrategy (see above) - auditStrategy.addEntityAtRevisionRestriction(globalCfg, qb, REFERENCED_ENTITY_ALIAS + "." + revisionPropertyPath, - REFERENCED_ENTITY_ALIAS + "." + verEntCfg.getRevisionEndFieldName(), false, - referencedIdData, revisionPropertyPath, originalIdPropertyName, REFERENCED_ENTITY_ALIAS, REFERENCED_ENTITY_ALIAS_DEF_AUD_STR); + /** + * Creates query restrictions used to retrieve only actual data. + */ + private void createValidDataRestrictions(GlobalConfiguration globalCfg, AuditStrategy auditStrategy, MiddleIdData referencedIdData, + String versionsMiddleEntityName, QueryBuilder qb, Parameters rootParameters, + boolean inclusive, MiddleComponentData... componentData) { + final String revisionPropertyPath = verEntCfg.getRevisionNumberPath(); + final String originalIdPropertyName = verEntCfg.getOriginalIdPropName(); + final String eeOriginalIdPropertyPath = MIDDLE_ENTITY_ALIAS + "." + originalIdPropertyName; + final String revisionTypePropName = getRevisionTypePath(); + // (selecting e entities at revision :revision) + // --> based on auditStrategy (see above) + auditStrategy.addEntityAtRevisionRestriction( + globalCfg, qb, rootParameters, REFERENCED_ENTITY_ALIAS + "." + revisionPropertyPath, + REFERENCED_ENTITY_ALIAS + "." + verEntCfg.getRevisionEndFieldName(), false, referencedIdData, revisionPropertyPath, + originalIdPropertyName, REFERENCED_ENTITY_ALIAS, REFERENCED_ENTITY_ALIAS_DEF_AUD_STR, inclusive + ); + // (with ee association at revision :revision) + // --> based on auditStrategy (see above) + auditStrategy.addAssociationAtRevisionRestriction( qb, rootParameters, revisionPropertyPath, + verEntCfg.getRevisionEndFieldName(), true, referencingIdData, versionsMiddleEntityName, + eeOriginalIdPropertyPath, revisionPropertyPath, originalIdPropertyName, MIDDLE_ENTITY_ALIAS, + inclusive, componentData + ); + // ee.revision_type != DEL + rootParameters.addWhereWithNamedParam( revisionTypePropName, "!=", DEL_REVISION_TYPE_PARAMETER ); + // e.revision_type != DEL + rootParameters.addWhereWithNamedParam( REFERENCED_ENTITY_ALIAS + "." + revisionTypePropName, false, "!=", DEL_REVISION_TYPE_PARAMETER ); + } - // (with ee association at revision :revision) - // --> based on auditStrategy (see above) - auditStrategy.addAssociationAtRevisionRestriction(qb, revisionPropertyPath, - verEntCfg.getRevisionEndFieldName(), true, referencingIdData, versionsMiddleEntityName, - eeOriginalIdPropertyPath, revisionPropertyPath, originalIdPropertyName, MIDDLE_ENTITY_ALIAS, componentDatas); - - // ee.revision_type != DEL - String revisionTypePropName = getRevisionTypePath(); - rootParameters.addWhereWithNamedParam(revisionTypePropName, "!=", DEL_REVISION_TYPE_PARAMETER); - // e.revision_type != DEL - rootParameters.addWhereWithNamedParam(REFERENCED_ENTITY_ALIAS + "." + revisionTypePropName, false, "!=", DEL_REVISION_TYPE_PARAMETER); - - StringBuilder sb = new StringBuilder(); - qb.build(sb, Collections.emptyMap()); - queryString = sb.toString(); - } + /** + * Create query restrictions used to retrieve actual data and deletions that took place at exactly given revision. + */ + private void createValidAndRemovedDataRestrictions(GlobalConfiguration globalCfg, AuditStrategy auditStrategy, + MiddleIdData referencedIdData, String versionsMiddleEntityName, + QueryBuilder remQb, MiddleComponentData... componentData) { + final Parameters disjoint = remQb.getRootParameters().addSubParameters( "or" ); + final Parameters valid = disjoint.addSubParameters( "and" ); // Restrictions to match all valid rows. + final Parameters removed = disjoint.addSubParameters( "and" ); // Restrictions to match all rows deleted at exactly given revision. + final String revisionPropertyPath = verEntCfg.getRevisionNumberPath(); + final String revisionTypePropName = getRevisionTypePath(); + // Excluding current revision, because we need to match data valid at the previous one. + createValidDataRestrictions( globalCfg, auditStrategy, referencedIdData, versionsMiddleEntityName, remQb, valid, false, componentData ); + // ee.revision = :revision + removed.addWhereWithNamedParam( revisionPropertyPath, "=", REVISION_PARAMETER ); + // e.revision = :revision + removed.addWhereWithNamedParam( REFERENCED_ENTITY_ALIAS + "." + revisionPropertyPath, false, "=", REVISION_PARAMETER ); + // ee.revision_type = DEL + removed.addWhereWithNamedParam( revisionTypePropName, "=", DEL_REVISION_TYPE_PARAMETER ); + // e.revision_type = DEL + removed.addWhereWithNamedParam( REFERENCED_ENTITY_ALIAS + "." + revisionTypePropName, false, "=", DEL_REVISION_TYPE_PARAMETER ); + } @Override protected String getQueryString() { return queryString; } -} + + @Override + protected String getQueryRemovedString() { + return queryRemovedString; + } +} \ No newline at end of file diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/impl/EntitiesAtRevisionQuery.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/impl/EntitiesAtRevisionQuery.java index e2df3aafe2..dc9677a0d2 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/impl/EntitiesAtRevisionQuery.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/impl/EntitiesAtRevisionQuery.java @@ -91,9 +91,9 @@ public class EntitiesAtRevisionQuery extends AbstractAuditQuery { // (selecting e entities at revision :revision) // --> based on auditStrategy (see above) - verCfg.getAuditStrategy().addEntityAtRevisionRestriction(verCfg.getGlobalCfg(), qb, revisionPropertyPath, - verEntCfg.getRevisionEndFieldName(), true, referencedIdData, - revisionPropertyPath, originalIdPropertyName, REFERENCED_ENTITY_ALIAS, REFERENCED_ENTITY_ALIAS_DEF_AUD_STR); + verCfg.getAuditStrategy().addEntityAtRevisionRestriction(verCfg.getGlobalCfg(), qb, qb.getRootParameters(), + revisionPropertyPath, verEntCfg.getRevisionEndFieldName(), true, referencedIdData, + revisionPropertyPath, originalIdPropertyName, REFERENCED_ENTITY_ALIAS, REFERENCED_ENTITY_ALIAS_DEF_AUD_STR, true); if (!includeDeletions) { // e.revision_type != DEL diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/strategy/AuditStrategy.java b/hibernate-envers/src/main/java/org/hibernate/envers/strategy/AuditStrategy.java index 3a84d1ecb3..c1c6f3cdd6 100755 --- a/hibernate-envers/src/main/java/org/hibernate/envers/strategy/AuditStrategy.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/strategy/AuditStrategy.java @@ -9,6 +9,7 @@ import org.hibernate.envers.configuration.GlobalConfiguration; import org.hibernate.envers.entities.mapper.PersistentCollectionChangeData; import org.hibernate.envers.entities.mapper.relation.MiddleComponentData; import org.hibernate.envers.entities.mapper.relation.MiddleIdData; +import org.hibernate.envers.tools.query.Parameters; import org.hibernate.envers.tools.query.QueryBuilder; /** @@ -59,6 +60,7 @@ public interface AuditStrategy { * * @param globalCfg the {@link GlobalConfiguration} * @param rootQueryBuilder the {@link QueryBuilder} that will be updated + * @param parameters root parameters to which restrictions shall be added * @param revisionProperty property of the revision column * @param revisionEndProperty property of the revisionEnd column (only used for {@link ValidityAuditStrategy}) * @param addAlias {@code boolean} indicator if a left alias is needed @@ -67,10 +69,11 @@ public interface AuditStrategy { * @param originalIdPropertyName name of the id property (only used for {@link ValidityAuditStrategy}) * @param alias1 an alias used for subquery (only used for {@link ValidityAuditStrategy}) * @param alias2 an alias used for subquery (only used for {@link ValidityAuditStrategy}) + * @param inclusive indicates whether revision number shall be treated as inclusive or exclusive */ - void addEntityAtRevisionRestriction(GlobalConfiguration globalCfg, QueryBuilder rootQueryBuilder, + void addEntityAtRevisionRestriction(GlobalConfiguration globalCfg, QueryBuilder rootQueryBuilder, Parameters parameters, String revisionProperty, String revisionEndProperty, boolean addAlias, MiddleIdData idData, - String revisionPropertyPath, String originalIdPropertyName, String alias1, String alias2); + String revisionPropertyPath, String originalIdPropertyName, String alias1, String alias2, boolean inclusive); /** * Update the rootQueryBuilder with an extra WHERE clause to restrict the revision for a middle-entity @@ -85,6 +88,7 @@ public interface AuditStrategy { * * * @param rootQueryBuilder the {@link QueryBuilder} that will be updated + * @param parameters root parameters to which restrictions shall be added * @param revisionProperty property of the revision column * @param revisionEndProperty property of the revisionEnd column (only used for {@link ValidityAuditStrategy}) * @param addAlias {@code boolean} indicator if a left alias is needed @@ -94,11 +98,11 @@ public interface AuditStrategy { * @param revisionPropertyPath path of the revision property (only used for {@link ValidityAuditStrategy}) * @param originalIdPropertyName name of the id property (only used for {@link ValidityAuditStrategy}) * @param alias1 an alias used for subqueries (only used for {@link DefaultAuditStrategy}) + * @param inclusive indicates whether revision number shall be treated as inclusive or exclusive * @param componentDatas information about the middle-entity relation */ - void addAssociationAtRevisionRestriction(QueryBuilder rootQueryBuilder, String revisionProperty, + void addAssociationAtRevisionRestriction(QueryBuilder rootQueryBuilder, Parameters parameters, String revisionProperty, String revisionEndProperty, boolean addAlias, MiddleIdData referencingIdData, String versionsMiddleEntityName, String eeOriginalIdPropertyPath, String revisionPropertyPath, - String originalIdPropertyName, String alias1, MiddleComponentData... componentDatas); - + String originalIdPropertyName, String alias1, boolean inclusive, MiddleComponentData... componentDatas); } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/strategy/DefaultAuditStrategy.java b/hibernate-envers/src/main/java/org/hibernate/envers/strategy/DefaultAuditStrategy.java index 5959c75d94..58c9c68306 100755 --- a/hibernate-envers/src/main/java/org/hibernate/envers/strategy/DefaultAuditStrategy.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/strategy/DefaultAuditStrategy.java @@ -41,11 +41,9 @@ public class DefaultAuditStrategy implements AuditStrategy { } - public void addEntityAtRevisionRestriction(GlobalConfiguration globalCfg, QueryBuilder rootQueryBuilder, String revisionProperty, - String revisionEndProperty, boolean addAlias, MiddleIdData idData, String revisionPropertyPath, - String originalIdPropertyName, String alias1, String alias2) { - Parameters rootParameters = rootQueryBuilder.getRootParameters(); - + public void addEntityAtRevisionRestriction(GlobalConfiguration globalCfg, QueryBuilder rootQueryBuilder, Parameters parameters, + String revisionProperty, String revisionEndProperty, boolean addAlias, MiddleIdData idData, String revisionPropertyPath, + String originalIdPropertyName, String alias1, String alias2, boolean inclusive) { // create a subquery builder // SELECT max(e.revision) FROM versionsReferencedEntity e2 QueryBuilder maxERevQb = rootQueryBuilder.newSubQueryBuilder(idData.getAuditEntityName(), alias2); @@ -53,29 +51,27 @@ public class DefaultAuditStrategy implements AuditStrategy { // WHERE Parameters maxERevQbParameters = maxERevQb.getRootParameters(); // e2.revision <= :revision - maxERevQbParameters.addWhereWithNamedParam(revisionPropertyPath, "<=", REVISION_PARAMETER); + maxERevQbParameters.addWhereWithNamedParam(revisionPropertyPath, inclusive ? "<=" : "<", REVISION_PARAMETER); // e2.id_ref_ed = e.id_ref_ed idData.getOriginalMapper().addIdsEqualToQuery(maxERevQbParameters, alias1 + "." + originalIdPropertyName, alias2 +"." + originalIdPropertyName); // add subquery to rootParameters String subqueryOperator = globalCfg.getCorrelatedSubqueryOperator(); - rootParameters.addWhere(revisionProperty, addAlias, subqueryOperator, maxERevQb); + parameters.addWhere(revisionProperty, addAlias, subqueryOperator, maxERevQb); } - public void addAssociationAtRevisionRestriction(QueryBuilder rootQueryBuilder, String revisionProperty, + public void addAssociationAtRevisionRestriction(QueryBuilder rootQueryBuilder, Parameters parameters, String revisionProperty, String revisionEndProperty, boolean addAlias, MiddleIdData referencingIdData, String versionsMiddleEntityName, - String eeOriginalIdPropertyPath, String revisionPropertyPath, - String originalIdPropertyName, String alias1, MiddleComponentData... componentDatas) { - Parameters rootParameters = rootQueryBuilder.getRootParameters(); - - // SELECT max(ee2.revision) FROM middleEntity ee2 + String eeOriginalIdPropertyPath, String revisionPropertyPath, String originalIdPropertyName, String alias1, + boolean inclusive, MiddleComponentData... componentDatas) { + // SELECT max(ee2.revision) FROM middleEntity ee2 QueryBuilder maxEeRevQb = rootQueryBuilder.newSubQueryBuilder(versionsMiddleEntityName, MIDDLE_ENTITY_ALIAS_DEF_AUD_STR); maxEeRevQb.addProjection("max", revisionPropertyPath, false); // WHERE Parameters maxEeRevQbParameters = maxEeRevQb.getRootParameters(); // ee2.revision <= :revision - maxEeRevQbParameters.addWhereWithNamedParam(revisionPropertyPath, "<=", REVISION_PARAMETER); + maxEeRevQbParameters.addWhereWithNamedParam(revisionPropertyPath, inclusive ? "<=" : "<", REVISION_PARAMETER); // ee2.originalId.* = ee.originalId.* String ee2OriginalIdPropertyPath = MIDDLE_ENTITY_ALIAS_DEF_AUD_STR + "." + originalIdPropertyName; referencingIdData.getPrefixedMapper().addIdsEqualToQuery(maxEeRevQbParameters, eeOriginalIdPropertyPath, ee2OriginalIdPropertyPath); @@ -84,7 +80,7 @@ public class DefaultAuditStrategy implements AuditStrategy { } // add subquery to rootParameters - rootParameters.addWhere(revisionProperty, addAlias, "=", maxEeRevQb); + parameters.addWhere(revisionProperty, addAlias, "=", maxEeRevQb); } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/strategy/ValidityAuditStrategy.java b/hibernate-envers/src/main/java/org/hibernate/envers/strategy/ValidityAuditStrategy.java index 6e2c6cc808..ee554b2999 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/strategy/ValidityAuditStrategy.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/strategy/ValidityAuditStrategy.java @@ -85,7 +85,6 @@ public class ValidityAuditStrategy implements AuditStrategy { final Serializable id, Object data, final Object revision) { - final AuditEntitiesConfiguration audEntitiesCfg = auditCfg.getAuditEntCfg(); final String auditedEntityName = audEntitiesCfg.getAuditEntityName( entityName ); final String revisionInfoEntityName = auditCfg.getAuditEntCfg().getRevisionInfoEntityName(); @@ -290,32 +289,29 @@ public class ValidityAuditStrategy implements AuditStrategy { } public void addEntityAtRevisionRestriction(GlobalConfiguration globalCfg, QueryBuilder rootQueryBuilder, - String revisionProperty,String revisionEndProperty, boolean addAlias, + Parameters parameters, String revisionProperty,String revisionEndProperty, boolean addAlias, MiddleIdData idData, String revisionPropertyPath, String originalIdPropertyName, - String alias1, String alias2) { - Parameters rootParameters = rootQueryBuilder.getRootParameters(); - addRevisionRestriction(rootParameters, revisionProperty, revisionEndProperty, addAlias); + String alias1, String alias2, boolean inclusive) { + addRevisionRestriction(parameters, revisionProperty, revisionEndProperty, addAlias, inclusive); } - public void addAssociationAtRevisionRestriction(QueryBuilder rootQueryBuilder, String revisionProperty, + public void addAssociationAtRevisionRestriction(QueryBuilder rootQueryBuilder, Parameters parameters, String revisionProperty, String revisionEndProperty, boolean addAlias, MiddleIdData referencingIdData, String versionsMiddleEntityName, String eeOriginalIdPropertyPath, String revisionPropertyPath, - String originalIdPropertyName, String alias1, MiddleComponentData... componentDatas) { - Parameters rootParameters = rootQueryBuilder.getRootParameters(); - addRevisionRestriction(rootParameters, revisionProperty, revisionEndProperty, addAlias); + String originalIdPropertyName, String alias1, boolean inclusive, MiddleComponentData... componentDatas) { + addRevisionRestriction(parameters, revisionProperty, revisionEndProperty, addAlias, inclusive); } public void setRevisionTimestampGetter(Getter revisionTimestampGetter) { this.revisionTimestampGetter = revisionTimestampGetter; } - private void addRevisionRestriction(Parameters rootParameters, - String revisionProperty, String revisionEndProperty, boolean addAlias) { - + private void addRevisionRestriction(Parameters rootParameters, String revisionProperty, String revisionEndProperty, + boolean addAlias, boolean inclusive) { // e.revision <= _revision and (e.endRevision > _revision or e.endRevision is null) Parameters subParm = rootParameters.addSubParameters("or"); - rootParameters.addWhereWithNamedParam(revisionProperty, addAlias, "<=", REVISION_PARAMETER); - subParm.addWhereWithNamedParam(revisionEndProperty + ".id", addAlias, ">", REVISION_PARAMETER); + rootParameters.addWhereWithNamedParam(revisionProperty, addAlias, inclusive ? "<=" : "<", REVISION_PARAMETER); + subParm.addWhereWithNamedParam(revisionEndProperty + ".id", addAlias, inclusive ? ">" : ">=", REVISION_PARAMETER); subParm.addWhere(revisionEndProperty, addAlias, "is", "null", false); } @@ -327,7 +323,6 @@ public class ValidityAuditStrategy implements AuditStrategy { @SuppressWarnings({"unchecked"}) private void updateLastRevision(Session session, AuditConfiguration auditCfg, List l, Object id, String auditedEntityName, Object revision) { - // There should be one entry if (l.size() == 1) { // Setting the end revision to be the current rev diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/tools/MutableInteger.java b/hibernate-envers/src/main/java/org/hibernate/envers/tools/MutableInteger.java index a158eb45e1..ba590dc231 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/tools/MutableInteger.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/tools/MutableInteger.java @@ -36,6 +36,10 @@ public class MutableInteger { this.value = value; } + public MutableInteger deepCopy() { + return new MutableInteger( value ); + } + public int getAndIncrease() { return value++; } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/tools/Pair.java b/hibernate-envers/src/main/java/org/hibernate/envers/tools/Pair.java index af968ebdff..8e9f0a01f6 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/tools/Pair.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/tools/Pair.java @@ -31,8 +31,8 @@ package org.hibernate.envers.tools; * @author Adam Warski (adamw@aster.pl) */ public class Pair { - private T1 obj1; - private T2 obj2; + private final T1 obj1; + private final T2 obj2; public Pair(T1 obj1, T2 obj2) { this.obj1 = obj1; diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/tools/query/Parameters.java b/hibernate-envers/src/main/java/org/hibernate/envers/tools/query/Parameters.java index 45bdc34581..6ecc43fc06 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/tools/query/Parameters.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/tools/query/Parameters.java @@ -79,6 +79,28 @@ public class Parameters { localQueryParamValues = new HashMap(); } + // Only for deep copy purpose. + private Parameters(Parameters other) { + this.alias = other.alias; + this.connective = other.connective; + this.queryParamCounter = other.queryParamCounter.deepCopy(); + + subParameters = new ArrayList( other.subParameters.size() ); + for ( Parameters p : other.subParameters ) { + subParameters.add( p.deepCopy() ); + } + negatedParameters = new ArrayList( other.negatedParameters.size() ); + for ( Parameters p : other.negatedParameters ) { + negatedParameters.add( p.deepCopy() ); + } + expressions = new ArrayList( other.expressions ); + localQueryParamValues = new HashMap( other.localQueryParamValues ); + } + + public Parameters deepCopy() { + return new Parameters( this ); + } + private String generateQueryParam() { return "_p" + queryParamCounter.getAndIncrease(); } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/tools/query/QueryBuilder.java b/hibernate-envers/src/main/java/org/hibernate/envers/tools/query/QueryBuilder.java index 63ba0fb7d7..87860e86b5 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/tools/query/QueryBuilder.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/tools/query/QueryBuilder.java @@ -92,6 +92,23 @@ public class QueryBuilder { addFrom(entityName, alias); } + // Only for deep copy purpose. + private QueryBuilder(QueryBuilder other) { + this.entityName = other.entityName; + this.alias = other.alias; + this.aliasCounter = other.aliasCounter.deepCopy(); + this.paramCounter = other.paramCounter.deepCopy(); + this.rootParameters = other.rootParameters.deepCopy(); + + froms = new ArrayList>( other.froms ); + orders = new ArrayList>( other.orders ); + projections = new ArrayList( other.projections ); + } + + public QueryBuilder deepCopy() { + return new QueryBuilder( this ); + } + /** * Add an entity from which to select. * @param entityName Name of the entity from which to select. diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/proxy/RemovedObjectQueryTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/proxy/RemovedObjectQueryTest.java index 9ff95b3429..5027a572da 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/proxy/RemovedObjectQueryTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/proxy/RemovedObjectQueryTest.java @@ -1,83 +1,444 @@ package org.hibernate.envers.test.integration.proxy; -import org.hibernate.Hibernate; -import org.hibernate.envers.RevisionType; -import org.hibernate.envers.query.AuditEntity; -import org.hibernate.envers.query.AuditQuery; -import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; -import org.hibernate.envers.test.Priority; -import org.hibernate.envers.test.entities.onetomany.SetRefEdEntity; -import org.hibernate.envers.test.entities.onetomany.SetRefIngEntity; -import org.hibernate.envers.test.tools.TestTools; -import org.hibernate.testing.FailureExpected; -import org.hibernate.testing.TestForIssue; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.persistence.EntityManager; + import org.junit.Assert; import org.junit.Test; -import java.util.List; -import java.util.Map; - -import javax.persistence.EntityManager; +import org.hibernate.Hibernate; +import org.hibernate.envers.RevisionType; +import org.hibernate.envers.enhanced.SequenceIdRevisionEntity; +import org.hibernate.envers.query.AuditEntity; +import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; +import org.hibernate.envers.test.Priority; +import org.hibernate.envers.test.entities.IntTestPrivSeqEntity; +import org.hibernate.envers.test.entities.StrTestPrivSeqEntity; +import org.hibernate.envers.test.entities.UnversionedStrTestEntity; +import org.hibernate.envers.test.entities.collection.StringSetEntity; +import org.hibernate.envers.test.entities.manytomany.SetOwnedEntity; +import org.hibernate.envers.test.entities.manytomany.SetOwningEntity; +import org.hibernate.envers.test.entities.manytomany.unidirectional.M2MIndexedListTargetNotAuditedEntity; +import org.hibernate.envers.test.entities.onetomany.SetRefEdEntity; +import org.hibernate.envers.test.entities.onetomany.SetRefIngEntity; +import org.hibernate.envers.test.integration.manytomany.ternary.TernaryMapEntity; +import org.hibernate.envers.test.tools.TestTools; +import org.hibernate.testing.TestForIssue; +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ @TestForIssue(jiraKey = "HHH-5845") public class RemovedObjectQueryTest extends BaseEnversJPAFunctionalTestCase { - @Override - @SuppressWarnings("unchecked") - protected void addConfigOptions(Map options) { - options.put("org.hibernate.envers.store_data_at_delete", "true"); - } + private Integer stringSetId = null; + private Integer ternaryMapId = null; + private UnversionedStrTestEntity unversionedEntity1 = null; + private UnversionedStrTestEntity unversionedEntity2 = null; + private StrTestPrivSeqEntity stringEntity1 = null; + private StrTestPrivSeqEntity stringEntity2 = null; + private IntTestPrivSeqEntity intEntity1 = null; + private IntTestPrivSeqEntity intEntity2 = null; - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { SetRefEdEntity.class, SetRefIngEntity.class }; - } + @Override + protected void addConfigOptions(Map options) { + super.addConfigOptions( options ); + options.put( "org.hibernate.envers.store_data_at_delete", "true" ); + } - @Test - @Priority(10) - public void initData() { - EntityManager em = getEntityManager(); + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + SetRefEdEntity.class, SetRefIngEntity.class, SetOwnedEntity.class, SetOwningEntity.class, + StringSetEntity.class, UnversionedStrTestEntity.class, M2MIndexedListTargetNotAuditedEntity.class, + TernaryMapEntity.class, StrTestPrivSeqEntity.class, IntTestPrivSeqEntity.class + }; + } - SetRefEdEntity refEdEntity = new SetRefEdEntity(1, "Demo Data"); - SetRefIngEntity refIngEntity = new SetRefIngEntity(2, "Example Data", refEdEntity); + @Test + @Priority(10) + public void initData() { + EntityManager em = getEntityManager(); - em.getTransaction().begin(); - em.persist(refEdEntity); - em.persist(refIngEntity); - em.getTransaction().commit(); + SetRefEdEntity refEdEntity1 = new SetRefEdEntity( 1, "Demo Data 1" ); + SetRefIngEntity refIngEntity1 = new SetRefIngEntity( 2, "Example Data 1", refEdEntity1 ); - em.getTransaction().begin(); - refIngEntity = em.find(SetRefIngEntity.class, 2); - em.remove(refIngEntity); - em.remove(refEdEntity); - em.getTransaction().commit(); - } + // Revision 1 + em.getTransaction().begin(); + em.persist( refEdEntity1 ); + em.persist( refIngEntity1 ); + em.getTransaction().commit(); - @Test - public void testFindDeletedReference() { - AuditQuery query = getAuditReader().createQuery().forRevisionsOfEntity(SetRefIngEntity.class, false, true) - .add(AuditEntity.revisionType().eq(RevisionType.DEL)); - List queryResult = (List) query.getResultList(); + // Revision 2 - removing both object in the same revision + em.getTransaction().begin(); + refEdEntity1 = em.find( SetRefEdEntity.class, 1 ); + refIngEntity1 = em.find( SetRefIngEntity.class, 2 ); + em.remove( refIngEntity1 ); + em.remove( refEdEntity1 ); + em.getTransaction().commit(); - Object[] objArray = (Object[]) queryResult.get(0); - SetRefIngEntity refIngEntity = (SetRefIngEntity) objArray[0]; - Assert.assertEquals("Example Data", refIngEntity.getData()); + SetRefEdEntity refEdEntity2 = new SetRefEdEntity( 3, "Demo Data 2" ); + SetRefIngEntity refIngEntity2 = new SetRefIngEntity( 4, "Example Data 2", refEdEntity2 ); - Hibernate.initialize(refIngEntity.getReference()); - Assert.assertEquals("Demo Data", refIngEntity.getReference().getData()); - } + // Revision 3 + em.getTransaction().begin(); + em.persist( refEdEntity2 ); + em.persist( refIngEntity2 ); + em.getTransaction().commit(); - @FailureExpected(jiraKey = "HHH-5845") // TODO: doesn't work until collection queries are fixed - @Test - public void testFindDeletedReferring() { - AuditQuery query = getAuditReader().createQuery().forRevisionsOfEntity(SetRefEdEntity.class, false, true) - .add(AuditEntity.revisionType().eq(RevisionType.DEL)); - List queryResult = (List) query.getResultList(); + // Revision 4 - removing child object + em.getTransaction().begin(); + refIngEntity2 = em.find( SetRefIngEntity.class, 4 ); + em.remove( refIngEntity2 ); + em.getTransaction().commit(); - Object[] objArray = (Object[]) queryResult.get(0); - SetRefEdEntity refEdEntity = (SetRefEdEntity) objArray[0]; - Assert.assertEquals("Demo Data", refEdEntity.getData()); + // Revision 5 - removing parent object + em.getTransaction().begin(); + refEdEntity2 = em.find( SetRefEdEntity.class, 3 ); + em.remove( refEdEntity2 ); + em.getTransaction().commit(); - Hibernate.initialize(refEdEntity.getReffering()); - Assert.assertEquals(TestTools.makeSet(new SetRefIngEntity(2, "Example Data")), refEdEntity.getReffering()); - } -} + SetOwningEntity setOwningEntity1 = new SetOwningEntity( 5, "Demo Data 1" ); + SetOwnedEntity setOwnedEntity1 = new SetOwnedEntity( 6, "Example Data 1" ); + Set owning = new HashSet(); + Set owned = new HashSet(); + owning.add( setOwningEntity1 ); + owned.add( setOwnedEntity1 ); + setOwningEntity1.setReferences( owned ); + setOwnedEntity1.setReferencing( owning ); + + // Revision 6 + em.getTransaction().begin(); + em.persist( setOwnedEntity1 ); + em.persist( setOwningEntity1 ); + em.getTransaction().commit(); + + // Revision 7 - removing both object in the same revision + em.getTransaction().begin(); + setOwnedEntity1 = em.find( SetOwnedEntity.class, 6 ); + setOwningEntity1 = em.find( SetOwningEntity.class, 5 ); + em.remove( setOwningEntity1 ); + em.remove( setOwnedEntity1 ); + em.getTransaction().commit(); + + SetOwningEntity setOwningEntity2 = new SetOwningEntity( 7, "Demo Data 2" ); + SetOwnedEntity setOwnedEntity2 = new SetOwnedEntity( 8, "Example Data 2" ); + owning = new HashSet(); + owned = new HashSet(); + owning.add( setOwningEntity2 ); + owned.add( setOwnedEntity2 ); + setOwningEntity2.setReferences( owned ); + setOwnedEntity2.setReferencing( owning ); + + // Revision 8 + em.getTransaction().begin(); + em.persist( setOwnedEntity2 ); + em.persist( setOwningEntity2 ); + em.getTransaction().commit(); + + // Revision 9 - removing first object + em.getTransaction().begin(); + setOwningEntity2 = em.find( SetOwningEntity.class, 7 ); + em.remove( setOwningEntity2 ); + em.getTransaction().commit(); + + // Revision 10 - removing second object + em.getTransaction().begin(); + setOwnedEntity2 = em.find( SetOwnedEntity.class, 8 ); + em.remove( setOwnedEntity2 ); + em.getTransaction().commit(); + + StringSetEntity stringSetEntity = new StringSetEntity(); + stringSetEntity.getStrings().add( "string 1" ); + stringSetEntity.getStrings().add( "string 2" ); + + // Revision 11 + em.getTransaction().begin(); + em.persist( stringSetEntity ); + em.getTransaction().commit(); + + stringSetId = stringSetEntity.getId(); + + // Revision 12 - removing element collection + em.getTransaction().begin(); + stringSetEntity = em.find( StringSetEntity.class, stringSetEntity.getId() ); + em.remove( stringSetEntity ); + em.getTransaction().commit(); + + // Revision 13 + em.getTransaction().begin(); + unversionedEntity1 = new UnversionedStrTestEntity( "string 1" ); + unversionedEntity2 = new UnversionedStrTestEntity( "string 2" ); + M2MIndexedListTargetNotAuditedEntity relationNotAuditedEntity = new M2MIndexedListTargetNotAuditedEntity( 1, "Parent" ); + relationNotAuditedEntity.getReferences().add( unversionedEntity1 ); + relationNotAuditedEntity.getReferences().add( unversionedEntity2 ); + em.persist( unversionedEntity1 ); + em.persist( unversionedEntity2 ); + em.persist( relationNotAuditedEntity ); + em.getTransaction().commit(); + + // Revision 14 - removing entity with unversioned relation + em.getTransaction().begin(); + relationNotAuditedEntity = em.find( M2MIndexedListTargetNotAuditedEntity.class, relationNotAuditedEntity.getId() ); + em.remove( relationNotAuditedEntity ); + em.getTransaction().commit(); + + stringEntity1 = new StrTestPrivSeqEntity( "Value 1" ); + stringEntity2 = new StrTestPrivSeqEntity( "Value 2" ); + intEntity1 = new IntTestPrivSeqEntity( 1 ); + intEntity2 = new IntTestPrivSeqEntity( 2 ); + TernaryMapEntity mapEntity = new TernaryMapEntity(); + mapEntity.getMap().put( intEntity1, stringEntity1 ); + mapEntity.getMap().put( intEntity2, stringEntity2 ); + + // Revision 15 + em.getTransaction().begin(); + em.persist( stringEntity1 ); + em.persist( stringEntity2 ); + em.persist( intEntity1 ); + em.persist( intEntity2 ); + em.persist( mapEntity ); + em.getTransaction().commit(); + + ternaryMapId = mapEntity.getId(); + + // Revision 16 - removing ternary map + em.getTransaction().begin(); + mapEntity = em.find( TernaryMapEntity.class, mapEntity.getId() ); + em.remove( mapEntity ); + em.getTransaction().commit(); + + em.close(); + } + + @Test + public void testTernaryMap() { + List queryResult = getAuditReader().createQuery().forRevisionsOfEntity( TernaryMapEntity.class, false, true ) + .add( AuditEntity.id().eq( ternaryMapId ) ) + .add( AuditEntity.revisionType().eq( RevisionType.DEL ) ) + .getResultList(); + Object[] objArray = (Object[]) queryResult.get( 0 ); + + Assert.assertEquals( 16, getRevisionNumber( objArray[1] ) ); + + TernaryMapEntity mapEntity = (TernaryMapEntity) objArray[0]; + Assert.assertEquals( TestTools.makeMap( intEntity1, stringEntity1, intEntity2, stringEntity2 ), mapEntity.getMap() ); + } + + @Test + public void testUnversionedRelation() { + List queryResult = getAuditReader().createQuery().forRevisionsOfEntity( M2MIndexedListTargetNotAuditedEntity.class, false, true ) + .add( AuditEntity.id().eq( 1 ) ) + .add( AuditEntity.revisionType().eq( RevisionType.DEL ) ) + .getResultList(); + Object[] objArray = (Object[]) queryResult.get( 0 ); + + Assert.assertEquals( 14, getRevisionNumber( objArray[1] ) ); + + M2MIndexedListTargetNotAuditedEntity relationNotAuditedEntity = (M2MIndexedListTargetNotAuditedEntity) objArray[0]; + Assert.assertTrue( + TestTools.checkList( + relationNotAuditedEntity.getReferences(), + unversionedEntity1, unversionedEntity2 + ) + ); + } + + @Test + public void testElementCollection() { + List queryResult = getAuditReader().createQuery().forRevisionsOfEntity( StringSetEntity.class, false, true ) + .add( AuditEntity.id().eq( stringSetId ) ) + .add( AuditEntity.revisionType().eq( RevisionType.DEL ) ) + .getResultList(); + Object[] objArray = (Object[]) queryResult.get( 0 ); + + Assert.assertEquals( 12, getRevisionNumber( objArray[1] ) ); + + StringSetEntity stringSetEntity = (StringSetEntity) objArray[0]; + Assert.assertEquals( TestTools.makeSet( "string 1", "string 2" ), stringSetEntity.getStrings() ); + } + + // One to many tests. + + @Test + public void testReferencedOneToManySameRevision() { + List queryResult = getAuditReader().createQuery().forRevisionsOfEntity( SetRefIngEntity.class, false, true ) + .add( AuditEntity.id().eq( 2 ) ) + .add( AuditEntity.revisionType().eq( RevisionType.DEL ) ) + .getResultList(); + Object[] objArray = (Object[]) queryResult.get( 0 ); + + Assert.assertEquals( 2, getRevisionNumber( objArray[1] ) ); + + SetRefIngEntity refIngEntity = (SetRefIngEntity) objArray[0]; + Assert.assertEquals( "Example Data 1", refIngEntity.getData() ); + + Hibernate.initialize( refIngEntity.getReference() ); + Assert.assertEquals( "Demo Data 1", refIngEntity.getReference().getData() ); + } + + @Test + public void testReferringOneToManySameRevision() { + List queryResult = getAuditReader().createQuery().forRevisionsOfEntity( SetRefEdEntity.class, false, true ) + .add( AuditEntity.id().eq( 1 ) ) + .add( AuditEntity.revisionType().eq( RevisionType.DEL ) ) + .getResultList(); + Object[] objArray = (Object[]) queryResult.get( 0 ); + + Assert.assertEquals( 2, getRevisionNumber( objArray[1] ) ); + + SetRefEdEntity refEdEntity = (SetRefEdEntity) objArray[0]; + Assert.assertEquals( "Demo Data 1", refEdEntity.getData() ); + + Hibernate.initialize( refEdEntity.getReffering() ); + Assert.assertEquals( + TestTools.makeSet( new SetRefIngEntity( 2, "Example Data 1" ) ), + refEdEntity.getReffering() + ); + } + + @Test + public void testReferencedOneToManyDifferentRevisions() { + List queryResult = getAuditReader().createQuery().forRevisionsOfEntity( SetRefIngEntity.class, false, true ) + .add( AuditEntity.id().eq( 4 ) ) + .add( AuditEntity.revisionType().eq( RevisionType.DEL ) ) + .getResultList(); + Object[] objArray = (Object[]) queryResult.get( 0 ); + + Assert.assertEquals( 4, getRevisionNumber( objArray[1] ) ); + + SetRefIngEntity refIngEntity = (SetRefIngEntity) objArray[0]; + Assert.assertEquals( "Example Data 2", refIngEntity.getData() ); + + Hibernate.initialize( refIngEntity.getReference() ); + Assert.assertEquals( "Demo Data 2", refIngEntity.getReference().getData() ); + } + + @Test + public void testReferringOneToManyDifferentRevisions() { + List queryResult = getAuditReader().createQuery().forRevisionsOfEntity( SetRefEdEntity.class, false, true ) + .add( AuditEntity.id().eq( 3 ) ) + .add( AuditEntity.revisionType().eq( RevisionType.DEL ) ) + .getResultList(); + Object[] objArray = (Object[]) queryResult.get( 0 ); + + Assert.assertEquals( 5, getRevisionNumber( objArray[1] ) ); + + SetRefEdEntity refEdEntity = (SetRefEdEntity) objArray[0]; + Assert.assertEquals( "Demo Data 2", refEdEntity.getData() ); + + Hibernate.initialize( refEdEntity.getReffering() ); + Assert.assertTrue( refEdEntity.getReffering().isEmpty() ); + + // After commit in revision four, child entity has been removed. + queryResult = getAuditReader().createQuery().forRevisionsOfEntity( SetRefEdEntity.class, false, true ) + .add( AuditEntity.id().eq( 3 ) ) + .add( AuditEntity.revisionNumber().eq( 4 ) ) + .getResultList(); + objArray = (Object[]) queryResult.get( 0 ); + + refEdEntity = (SetRefEdEntity) objArray[0]; + Assert.assertEquals( "Demo Data 2", refEdEntity.getData() ); + + Hibernate.initialize( refEdEntity.getReffering() ); + Assert.assertTrue( refEdEntity.getReffering().isEmpty() ); + } + + // Many to many tests. + + @Test + public void testOwnedManyToManySameRevision() { + List queryResult = getAuditReader().createQuery().forRevisionsOfEntity( SetOwningEntity.class, false, true ) + .add( AuditEntity.id().eq( 5 ) ) + .add( AuditEntity.revisionType().eq( RevisionType.DEL ) ) + .getResultList(); + Object[] objArray = (Object[]) queryResult.get( 0 ); + + Assert.assertEquals( 7, getRevisionNumber( objArray[1] ) ); + + SetOwningEntity setOwningEntity = (SetOwningEntity) objArray[0]; + Assert.assertEquals( "Demo Data 1", setOwningEntity.getData() ); + + Hibernate.initialize( setOwningEntity.getReferences() ); + Assert.assertEquals( + TestTools.makeSet( new SetOwnedEntity( 6, "Example Data 1" ) ), + setOwningEntity.getReferences() + ); + } + + @Test + public void testOwningManyToManySameRevision() { + List queryResult = getAuditReader().createQuery().forRevisionsOfEntity( SetOwnedEntity.class, false, true ) + .add( AuditEntity.id().eq( 6 ) ) + .add( AuditEntity.revisionType().eq( RevisionType.DEL ) ) + .getResultList(); + Object[] objArray = (Object[]) queryResult.get( 0 ); + + Assert.assertEquals( 7, getRevisionNumber( objArray[1] ) ); + + SetOwnedEntity setOwnedEntity = (SetOwnedEntity) objArray[0]; + Assert.assertEquals( "Example Data 1", setOwnedEntity.getData() ); + + Hibernate.initialize( setOwnedEntity.getReferencing() ); + Assert.assertEquals( + TestTools.makeSet( new SetOwningEntity( 5, "Demo Data 1" ) ), + setOwnedEntity.getReferencing() + ); + } + + @Test + public void testOwnedManyToManyDifferentRevisions() { + List queryResult = getAuditReader().createQuery().forRevisionsOfEntity( SetOwningEntity.class, false, true ) + .add( AuditEntity.id().eq( 7 ) ) + .add( AuditEntity.revisionType().eq( RevisionType.DEL ) ) + .getResultList(); + Object[] objArray = (Object[]) queryResult.get( 0 ); + + Assert.assertEquals( 9, getRevisionNumber( objArray[1] ) ); + + SetOwningEntity setOwningEntity = (SetOwningEntity) objArray[0]; + Assert.assertEquals( "Demo Data 2", setOwningEntity.getData() ); + + Hibernate.initialize( setOwningEntity.getReferences() ); + Assert.assertEquals( + TestTools.makeSet( new SetOwnedEntity( 8, "Example Data 2" ) ), + setOwningEntity.getReferences() + ); + } + + @Test + public void testOwningManyToManyDifferentRevisions() { + List queryResult = getAuditReader().createQuery().forRevisionsOfEntity( SetOwnedEntity.class, false, true ) + .add( AuditEntity.id().eq( 8 ) ) + .add( AuditEntity.revisionType().eq( RevisionType.DEL ) ) + .getResultList(); + Object[] objArray = (Object[]) queryResult.get( 0 ); + + Assert.assertEquals( 10, getRevisionNumber( objArray[1] ) ); + + SetOwnedEntity setOwnedEntity = (SetOwnedEntity) objArray[0]; + Assert.assertEquals( "Example Data 2", setOwnedEntity.getData() ); + + Hibernate.initialize( setOwnedEntity.getReferencing() ); + Assert.assertTrue( setOwnedEntity.getReferencing().isEmpty() ); + + // After commit in revision nine, related entity has been removed. + queryResult = getAuditReader().createQuery().forRevisionsOfEntity( SetOwnedEntity.class, false, true ) + .add( AuditEntity.id().eq( 8 ) ) + .add( AuditEntity.revisionNumber().eq( 9 ) ) + .getResultList(); + objArray = (Object[]) queryResult.get( 0 ); + + setOwnedEntity = (SetOwnedEntity) objArray[0]; + Assert.assertEquals( "Example Data 2", setOwnedEntity.getData() ); + + Hibernate.initialize( setOwnedEntity.getReferencing() ); + Assert.assertTrue( setOwnedEntity.getReferencing().isEmpty() ); + } + + private Number getRevisionNumber(Object revisionEntity) { + return ( (SequenceIdRevisionEntity) revisionEntity ).getId(); + } +} \ No newline at end of file