HHH-10762 -Implemented left joins for relation traversion in audit

queries by leveraging the new HQL feature to join unrelated entities.
Furthermore, the implementation of inner joins have been improved by
using the same new HQL feature. The audit query API has been extended to
support criterias where two properties of different entities are
disjuncted, conjuncted or directly compared to each other.
This commit is contained in:
Felix Feisst 2016-05-22 13:01:01 +02:00 committed by Chris Cranford
parent c947187748
commit a59ebb7e53
41 changed files with 976 additions and 341 deletions

View File

@ -77,17 +77,25 @@ public abstract class AbstractIdMapper implements IdMapper {
}
@Override
public void addIdEqualsToQuery(Parameters parameters, Object id, String prefix, boolean equals) {
public void addIdEqualsToQuery(Parameters parameters, Object id, String alias, String prefix, boolean equals) {
final List<QueryParameterData> paramDatas = mapToQueryParametersFromId( id );
final Parameters parametersToUse = getParametersToUse( parameters, paramDatas );
for ( QueryParameterData paramData : paramDatas ) {
if ( paramData.getValue() == null ) {
handleNullValue( parametersToUse, paramData.getProperty( prefix ), equals );
handleNullValue( parametersToUse, alias, paramData.getProperty( prefix ), equals );
}
else if ( alias == null ) {
parametersToUse.addWhereWithParam(
paramData.getProperty( prefix ),
equals ? "=" : "<>",
paramData.getValue()
);
}
else {
parametersToUse.addWhereWithParam(
alias,
paramData.getProperty( prefix ),
equals ? "=" : "<>",
paramData.getValue()
@ -111,12 +119,12 @@ public abstract class AbstractIdMapper implements IdMapper {
}
}
private void handleNullValue(Parameters parameters, String propertyName, boolean equals) {
private void handleNullValue(Parameters parameters, String alias, String propertyName, boolean equals) {
if ( equals ) {
parameters.addNullRestriction( propertyName, equals );
parameters.addNullRestriction( alias, propertyName );
}
else {
parameters.addNotNullRestriction( propertyName, equals );
parameters.addNotNullRestriction( alias, propertyName );
}
}
}

View File

@ -80,10 +80,11 @@ public interface IdMapper {
*
* @param parameters Parameters, to which to add the statements.
* @param id Value of id.
* @param alias the alias to use in the specified parameters (may be null).
* @param prefix Prefix to add to the properties (may be null).
* @param equals Should this query express the "=" relation or the "<>" relation.
*/
void addIdEqualsToQuery(Parameters parameters, Object id, String prefix, boolean equals);
void addIdEqualsToQuery(Parameters parameters, Object id, String alias, String prefix, boolean equals);
/**
* Adds query statements, which contains named parameters, which express the property that the id of the entity

View File

@ -72,7 +72,7 @@ public class CrossTypeRevisionChangesReaderImpl implements CrossTypeRevisionChan
type.getFirst(),
revision
)
.add( new RevisionTypeAuditExpression( revisionType, "=" ) ).getResultList()
.add( new RevisionTypeAuditExpression( null, revisionType, "=" ) ).getResultList()
);
}
return result;
@ -89,7 +89,7 @@ public class CrossTypeRevisionChangesReaderImpl implements CrossTypeRevisionChan
for ( Pair<String, Class> type : entityTypes ) {
final List<Object> list = auditReaderImplementor.createQuery()
.forEntitiesModifiedAtRevision( type.getSecond(), type.getFirst(), revision )
.add( new RevisionTypeAuditExpression( revisionType, "=" ) )
.add( new RevisionTypeAuditExpression( null, revisionType, "=" ) )
.getResultList();
result.get( revisionType ).addAll( list );
}

View File

@ -122,10 +122,6 @@ public class Parameters {
return newParams;
}
public void addWhere(String left, String op, String right) {
addWhere( left, true, op, right, true );
}
/**
* Adds <code>IS NULL</code> restriction.
*
@ -136,6 +132,16 @@ public class Parameters {
addWhere( propertyName, addAlias, "is", "null", false );
}
/**
* Adds <code>IS NULL</code> restriction.
*
* @param alias the alias which should be added to the property name.
* @param propertyName Property name.
*/
public void addNullRestriction(String alias, String propertyName) {
addWhere( alias, propertyName, "is", null, "null" );
}
/**
* Adds <code>IS NOT NULL</code> restriction.
*
@ -146,36 +152,45 @@ public class Parameters {
addWhere( propertyName, addAlias, "is not", "null", false );
}
/**
* Adds <code>IS NOT NULL</code> restriction.
*
* @param alias the alias which should be added to the property name.
* @param propertyName Property name.
*/
public void addNotNullRestriction(String alias, String propertyName) {
addWhere( alias, propertyName, "is not", null, "null" );
}
public void addWhere(String left, boolean addAliasLeft, String op, String right, boolean addAliasRight) {
addWhere(
addAliasLeft ? alias : null,
left,
op,
addAliasRight ? alias : null,
right
);
}
public void addWhere(String aliasLeft, String left, String op, String aliasRight, String right) {
final StringBuilder expression = new StringBuilder();
if ( addAliasLeft ) {
expression.append( alias ).append( "." );
if ( aliasLeft != null ) {
expression.append( aliasLeft ).append( "." );
}
expression.append( left );
expression.append( " " ).append( op ).append( " " );
if ( addAliasRight ) {
expression.append( alias ).append( "." );
if ( aliasRight != null ) {
expression.append( aliasRight ).append( "." );
}
expression.append( right );
expressions.add( expression.toString() );
}
// compare properties from two different entities (aliases)
public void addWhere(final String aliasLeft, final String left, final String op, final String aliasRight, final String right) {
final StringBuilder expression = new StringBuilder();
expression.append( aliasLeft ).append( '.' ).append( left );
expression.append( ' ' ).append( op ).append( ' ' );
expression.append( aliasRight ).append( '.' ).append( right );
expressions.add( expression.toString() );
}
public void addWhereWithFunction(String left, String leftFunction, String op, Object paramValue){
public void addWhereWithFunction(String alias, String left, String leftFunction, String op, Object paramValue){
final String paramName = generateQueryParam();
localQueryParamValues.put( paramName, paramValue );
@ -194,6 +209,11 @@ public class Parameters {
addWhereWithParam( left, true, op, paramValue );
}
public void addWhereWithParam(String alias, String left, String op, Object paramValue ) {
String effectiveLeft = alias.concat( "." ).concat( left );
addWhereWithParam( effectiveLeft, false, op, paramValue );
}
public void addWhereWithParam(String left, boolean addAlias, String op, Object paramValue) {
final String paramName = generateQueryParam();
localQueryParamValues.put( paramName, paramValue );
@ -218,7 +238,7 @@ public class Parameters {
expressions.add( expression.toString() );
}
public void addWhereWithParams(String left, String opStart, Object[] paramValues, String opEnd) {
public void addWhereWithParams(String alias, String left, String opStart, Object[] paramValues, String opEnd) {
final StringBuilder expression = new StringBuilder();
expression.append( alias ).append( "." ).append( left ).append( " " ).append( opStart );
@ -239,15 +259,20 @@ public class Parameters {
expressions.add( expression.toString() );
}
public void addWhere(String left, String op, QueryBuilder right) {
addWhere( left, true, op, right );
public void addWhere(String left, boolean addAlias, String op, QueryBuilder right) {
addWhere(
addAlias ? alias : null,
left,
op,
right
);
}
public void addWhere(String left, boolean addAlias, String op, QueryBuilder right) {
public void addWhere( String leftAlias, String left, String op, QueryBuilder right) {
final StringBuilder expression = new StringBuilder();
if ( addAlias ) {
expression.append( alias ).append( "." );
if ( leftAlias != null ) {
expression.append( leftAlias ).append( "." );
}
expression.append( left );

View File

@ -9,8 +9,11 @@ package org.hibernate.envers.internal.tools.query;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.persistence.criteria.JoinType;
import org.hibernate.Session;
import org.hibernate.envers.RevisionType;
import org.hibernate.envers.internal.entities.RevisionTypeType;
@ -46,7 +49,7 @@ public class QueryBuilder {
/**
* A list of triples (from entity name, alias name, whether to select the entity).
*/
private final List<Triple<String, String, Boolean>> froms;
private final List<JoinParameter> froms;
/**
* A list of triples (alias, property name, order ascending?).
*/
@ -114,21 +117,21 @@ public class QueryBuilder {
* @param select whether the entity should be selected
*/
public void addFrom(String entityName, String alias, boolean select) {
froms.add( Triple.make( entityName, alias, select ) );
CrossJoinParameter joinParameter = new CrossJoinParameter( entityName, alias, select );
froms.add( joinParameter );
}
public Parameters addJoin(JoinType joinType, String entityName, String alias, boolean select) {
Parameters joinConditionParameters = new Parameters( alias, Parameters.AND, paramCounter );
InnerOuterJoinParameter joinParameter = new InnerOuterJoinParameter( joinType, entityName, alias, select, joinConditionParameters );
froms.add( joinParameter );
return joinConditionParameters;
}
public String generateAlias() {
return "_e" + aliasCounter.getAndIncrease();
}
/**
* @return A sub-query builder for the same entity (with an auto-generated alias). The sub-query can
* be later used as a value of a parameter.
*/
public QueryBuilder newSubQueryBuilder() {
return newSubQueryBuilder( entityName, generateAlias() );
}
/**
* @param entityName Entity name, which will be the main entity for the sub-query.
* @param alias Alias of the entity, which can later be used in parameters.
@ -185,10 +188,14 @@ public class QueryBuilder {
StringTools.append( sb, getSelectAliasList().iterator(), ", " );
}
sb.append( " from " );
// all from entities with aliases, separated with commas
StringTools.append( sb, getFromList().iterator(), ", " );
// where part - rootParameters
// all from entities with aliases
boolean first = true;
for (final JoinParameter joinParameter : froms) {
joinParameter.appendJoin( first, sb, queryParamValues );
first = false;
}
// where part - rootParameters
first = true;
for (final Parameters params : parameters) {
if (!params.isEmpty()) {
if (first) {
@ -210,9 +217,9 @@ public class QueryBuilder {
private List<String> getSelectAliasList() {
final List<String> aliasList = new ArrayList<>();
for ( Triple<String, String, Boolean> from : froms ) {
if ( from.getThird() ) {
aliasList.add( from.getSecond() );
for ( JoinParameter from : froms ) {
if ( from.isSelect() ) {
aliasList.add( from.getAlias() );
}
}
@ -223,15 +230,6 @@ public class QueryBuilder {
return alias;
}
private List<String> getFromList() {
final List<String> fromList = new ArrayList<>();
for ( Triple<String, String, Boolean> from : froms ) {
fromList.add( from.getFirst() + " " + from.getSecond() );
}
return fromList;
}
private List<String> getOrderList() {
final List<String> orderList = new ArrayList<>();
for ( Triple<String, String, Boolean> order : orders ) {
@ -263,4 +261,73 @@ public class QueryBuilder {
}
return query;
}
private abstract static class JoinParameter {
private final String alias;
private final boolean select;
protected JoinParameter(String alias, boolean select) {
this.alias = alias;
this.select = select;
}
public String getAlias() {
return alias;
}
public boolean isSelect() {
return select;
}
public abstract void appendJoin(boolean firstFromElement, StringBuilder builder, Map<String, Object> queryParamValues);
}
private static class CrossJoinParameter extends JoinParameter {
private final String entityName;
public CrossJoinParameter(String entityName, String alias, boolean select) {
super( alias, select );
this.entityName = entityName;
}
@Override
public void appendJoin(final boolean firstFromElement, final StringBuilder builder, final Map<String, Object> queryParamValues) {
if (!firstFromElement) {
builder.append( ", " );
}
builder.append( entityName ).append( ' ' ).append( getAlias() );
}
}
private static class InnerOuterJoinParameter extends JoinParameter {
private final JoinType joinType;
private final String entityName;
private final Parameters joinConditionParameters;
public InnerOuterJoinParameter(JoinType joinType, String entityName, String alias, boolean select, Parameters joinConditionParameters) {
super(alias, select);
this.joinType = joinType;
this.entityName = entityName;
this.joinConditionParameters = joinConditionParameters;
}
@Override
public void appendJoin(boolean firstFromElement, StringBuilder builder, Map<String, Object> queryParamValues) {
if (firstFromElement) {
throw new IllegalArgumentException( "An inner/outer join cannot come as first 'from element'" );
}
builder.append( ' ' ).append( joinType.name()
.toLowerCase( Locale.US ) ).append( " join " )
.append( entityName ).append( ' ' )
.append( getAlias() ).append( " on " );
joinConditionParameters.build( builder, queryParamValues );
}
}
}

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.envers.query;
import javax.persistence.criteria.JoinType;
import org.hibernate.CacheMode;
import org.hibernate.FlushMode;
import org.hibernate.Incubating;
@ -20,6 +22,13 @@ import org.hibernate.envers.query.projection.AuditProjection;
@Incubating
public interface AuditAssociationQuery<Q extends AuditQuery> extends AuditQuery {
@Override
AuditAssociationQuery<? extends AuditAssociationQuery<Q>> traverseRelation(String associationName, JoinType joinType);
@Override
AuditAssociationQuery<? extends AuditAssociationQuery<Q>> traverseRelation(String associationName, JoinType joinType,
String alias);
@Override
AuditAssociationQuery<Q> add(AuditCriterion criterion);

View File

@ -35,7 +35,11 @@ public class AuditEntity {
}
public static AuditId id() {
return new AuditId();
return id( null );
}
public static AuditId id(String alias) {
return new AuditId( alias );
}
/**
@ -44,7 +48,17 @@ public class AuditEntity {
* @param propertyName Name of the property.
*/
public static AuditProperty<Object> property(String propertyName) {
return new AuditProperty<>( new EntityPropertyName( propertyName ) );
return property( null, propertyName );
}
/**
* Create restrictions, projections and specify order for a property of an audited entity.
*
* @param alias the alias of the entity which owns the property.
* @param propertyName Name of the property.
*/
public static AuditProperty<Object> property(String alias, String propertyName) {
return new AuditProperty<>( alias, new EntityPropertyName( propertyName ) );
}
/**
@ -52,7 +66,17 @@ public class AuditEntity {
* audited entity.
*/
public static AuditProperty<Number> revisionNumber() {
return new AuditProperty<>( new RevisionNumberPropertyName() );
return revisionNumber( null );
}
/**
* Create restrictions, projections and specify order for the revision number, corresponding to an
* audited entity.
*
* @param alias the alias of the entity which owns the revision number.
*/
public static AuditProperty<Number> revisionNumber(String alias) {
return new AuditProperty<>( alias, new RevisionNumberPropertyName() );
}
/**
@ -62,7 +86,18 @@ public class AuditEntity {
* @param propertyName Name of the property.
*/
public static AuditProperty<Object> revisionProperty(String propertyName) {
return new AuditProperty<>( new RevisionPropertyPropertyName( propertyName ) );
return revisionProperty( null, propertyName );
}
/**
* Create restrictions, projections and specify order for a property of the revision entity,
* corresponding to an audited entity.
*
* @param alias the alias of the entity which owns the revision property.
* @param propertyName Name of the property.
*/
public static AuditProperty<Object> revisionProperty(String alias, String propertyName) {
return new AuditProperty<>( alias, new RevisionPropertyPropertyName( propertyName ) );
}
/**
@ -70,7 +105,17 @@ public class AuditEntity {
* audited entity.
*/
public static AuditProperty<RevisionType> revisionType() {
return new AuditProperty<>( new RevisionTypePropertyName() );
return revisionType( null );
}
/**
* Create restrictions, projections and specify order for the revision type, corresponding to an
* audited entity.
*
* @param alias the alias of the entity which owns the revision type.
*/
public static AuditProperty<RevisionType> revisionType(String alias) {
return new AuditProperty<>( alias, new RevisionTypePropertyName() );
}
/**
@ -79,7 +124,17 @@ public class AuditEntity {
* @param propertyName Name of the property, which is the relation.
*/
public static AuditRelatedId relatedId(String propertyName) {
return new AuditRelatedId( new EntityPropertyName( propertyName ) );
return relatedId( null, propertyName );
}
/**
* Create restrictions on an id of a related entity.
*
* @param alias the alias of the entity which owns the relation property.
* @param propertyName Name of the property, which is the relation.
*/
public static AuditRelatedId relatedId(String alias, String propertyName) {
return new AuditRelatedId( alias, new EntityPropertyName( propertyName ) );
}
/**
@ -123,6 +178,6 @@ public class AuditEntity {
* @param distinct whether to distinct select the entity
*/
public static AuditProjection selectEntity(boolean distinct) {
return new EntityAuditProjection( distinct );
return new EntityAuditProjection( null, distinct );
}
}

View File

@ -30,6 +30,9 @@ public interface AuditQuery {
AuditAssociationQuery<? extends AuditQuery> traverseRelation(String associationName, JoinType joinType);
AuditAssociationQuery<? extends AuditQuery> traverseRelation(String associationName, JoinType joinType,
String alias);
AuditQuery add(AuditCriterion criterion);
AuditQuery addProjection(AuditProjection projection);
@ -53,4 +56,6 @@ public interface AuditQuery {
AuditQuery setTimeout(int timeout);
AuditQuery setLockMode(LockMode lockMode);
String getAlias();
}

View File

@ -8,6 +8,7 @@ package org.hibernate.envers.query.criteria;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
@ -21,13 +22,15 @@ import org.hibernate.envers.query.internal.property.PropertyNameGetter;
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class AggregatedAuditExpression implements AuditCriterion, ExtendableCriterion {
private String alias;
private PropertyNameGetter propertyNameGetter;
private AggregatedMode mode;
// Correlate subquery with outer query by entity id.
private boolean correlate;
private List<AuditCriterion> criterions;
public AggregatedAuditExpression(PropertyNameGetter propertyNameGetter, AggregatedMode mode) {
public AggregatedAuditExpression(String alias, PropertyNameGetter propertyNameGetter, AggregatedMode mode) {
this.alias = alias;
this.propertyNameGetter = propertyNameGetter;
this.mode = mode;
criterions = new ArrayList<>();
@ -48,10 +51,12 @@ public class AggregatedAuditExpression implements AuditCriterion, ExtendableCrit
public void addToQuery(
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
String alias,
Map<String, String> aliasToEntityNameMap,
String baseAlias,
QueryBuilder qb,
Parameters parameters) {
String effectiveAlias = this.alias == null ? baseAlias : this.alias;
String entityName = aliasToEntityNameMap.get( effectiveAlias );
String propertyName = CriteriaTools.determinePropertyName(
enversService,
versionsReader,
@ -64,13 +69,16 @@ public class AggregatedAuditExpression implements AuditCriterion, ExtendableCrit
// Make sure our conditions are ANDed together even if the parent Parameters have a different connective
Parameters subParams = parameters.addSubParameters( Parameters.AND );
// This will be the aggregated query, containing all the specified conditions
QueryBuilder subQb = qb.newSubQueryBuilder();
String auditEntityName = enversService.getAuditEntitiesConfiguration().getAuditEntityName( entityName );
String subQueryAlias = qb.generateAlias();
QueryBuilder subQb = qb.newSubQueryBuilder( auditEntityName, subQueryAlias );
aliasToEntityNameMap.put( subQueryAlias, entityName );
// Adding all specified conditions both to the main query, as well as to the
// aggregated one.
for ( AuditCriterion versionsCriteria : criterions ) {
versionsCriteria.addToQuery( enversService, versionsReader, entityName, qb.getAlias(), qb, subParams );
versionsCriteria.addToQuery( enversService, versionsReader, entityName, subQb.getAlias(), subQb, subQb.getRootParameters() );
versionsCriteria.addToQuery( enversService, versionsReader, aliasToEntityNameMap, effectiveAlias, qb, subParams );
versionsCriteria.addToQuery( enversService, versionsReader, aliasToEntityNameMap, subQueryAlias, subQb, subQb.getRootParameters() );
}
// Setting the desired projection of the aggregated query
@ -88,12 +96,12 @@ public class AggregatedAuditExpression implements AuditCriterion, ExtendableCrit
enversService.getEntitiesConfigurations().get( entityName ).getIdMapper().addIdsEqualToQuery(
subQb.getRootParameters(),
subQb.getRootAlias() + "." + originalIdPropertyName,
qb.getRootAlias() + "." + originalIdPropertyName
effectiveAlias + "." + originalIdPropertyName
);
}
// Adding the constrain on the result of the aggregated criteria
subParams.addWhere( propertyName, "=", subQb );
subParams.addWhere( effectiveAlias, propertyName, "=", subQb );
}
/**

View File

@ -8,6 +8,7 @@ package org.hibernate.envers.query.criteria;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
@ -34,7 +35,7 @@ public class AuditConjunction implements AuditCriterion, ExtendableCriterion {
public void addToQuery(
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
Map<String, String> aliasToEntityNameMap,
String alias,
QueryBuilder qb,
Parameters parameters) {
@ -45,7 +46,7 @@ public class AuditConjunction implements AuditCriterion, ExtendableCriterion {
}
else {
for ( AuditCriterion criterion : criterions ) {
criterion.addToQuery( enversService, versionsReader, entityName, alias, qb, andParameters );
criterion.addToQuery( enversService, versionsReader, aliasToEntityNameMap, alias, qb, andParameters );
}
}
}

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.envers.query.criteria;
import java.util.Map;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.Parameters;
@ -18,8 +20,8 @@ public interface AuditCriterion {
void addToQuery(
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
String alias,
Map<String, String> aliasToEntityNameMap,
String baseAlias,
QueryBuilder qb,
Parameters parameters);
}

View File

@ -8,6 +8,7 @@ package org.hibernate.envers.query.criteria;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
@ -34,7 +35,7 @@ public class AuditDisjunction implements AuditCriterion, ExtendableCriterion {
public void addToQuery(
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
Map<String, String> aliasToEntityNameMap,
String alias,
QueryBuilder qb,
Parameters parameters) {
@ -45,7 +46,7 @@ public class AuditDisjunction implements AuditCriterion, ExtendableCriterion {
}
else {
for ( AuditCriterion criterion : criterions ) {
criterion.addToQuery( enversService, versionsReader, entityName, alias, qb, orParameters );
criterion.addToQuery( enversService, versionsReader, aliasToEntityNameMap, alias, qb, orParameters );
}
}
}

View File

@ -21,8 +21,11 @@ public class AuditId<T> extends AuditProperty<T> {
public static final String IDENTIFIER_PLACEHOLDER = "$$id$$";
private static final PropertyNameGetter IDENTIFIER_PROPERTY_GETTER = new EntityPropertyName( IDENTIFIER_PLACEHOLDER );
public AuditId() {
super( IDENTIFIER_PROPERTY_GETTER );
private final String alias;
public AuditId(String alias) {
super( alias, IDENTIFIER_PROPERTY_GETTER );
this.alias = alias;
}
/**
@ -30,7 +33,7 @@ public class AuditId<T> extends AuditProperty<T> {
*/
@Override
public AuditCriterion eq(Object id) {
return new IdentifierEqAuditExpression( id, true );
return new IdentifierEqAuditExpression( alias, id, true );
}
/**
@ -38,7 +41,7 @@ public class AuditId<T> extends AuditProperty<T> {
*/
@Override
public AuditCriterion ne(Object id) {
return new IdentifierEqAuditExpression( id, false );
return new IdentifierEqAuditExpression( alias, id, false );
}
// Projections

View File

@ -11,7 +11,6 @@ import java.util.Collection;
import org.hibernate.criterion.MatchMode;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.internal.entities.EntityInstantiator;
import org.hibernate.envers.internal.tools.Triple;
import org.hibernate.envers.query.criteria.internal.BetweenAuditExpression;
import org.hibernate.envers.query.criteria.internal.IlikeAuditExpression;
import org.hibernate.envers.query.criteria.internal.InAuditExpression;
@ -34,165 +33,257 @@ import org.hibernate.envers.query.projection.internal.PropertyAuditProjection;
*/
@SuppressWarnings({"JavaDoc"})
public class AuditProperty<T> implements AuditProjection {
private final String alias;
private final PropertyNameGetter propertyNameGetter;
public AuditProperty(PropertyNameGetter propertyNameGetter) {
public AuditProperty(String alias, PropertyNameGetter propertyNameGetter) {
this.alias = alias;
this.propertyNameGetter = propertyNameGetter;
}
public AuditCriterion hasChanged() {
return new SimpleAuditExpression( new ModifiedFlagPropertyName( propertyNameGetter ), true, "=" );
return new SimpleAuditExpression( alias, new ModifiedFlagPropertyName( propertyNameGetter ), true, "=" );
}
public AuditCriterion hasNotChanged() {
return new SimpleAuditExpression( new ModifiedFlagPropertyName( propertyNameGetter ), false, "=" );
return new SimpleAuditExpression( alias, new ModifiedFlagPropertyName( propertyNameGetter ), false, "=" );
}
/**
* Apply an "equal" constraint
*/
public AuditCriterion eq(T value) {
return new SimpleAuditExpression( propertyNameGetter, value, "=" );
return new SimpleAuditExpression( alias, propertyNameGetter, value, "=" );
}
/**
* Apply a "not equal" constraint
*/
public AuditCriterion ne(T value) {
return new SimpleAuditExpression( propertyNameGetter, value, "<>" );
return new SimpleAuditExpression( alias, propertyNameGetter, value, "<>" );
}
/**
* Apply a "like" constraint
*/
public AuditCriterion like(T value) {
return new SimpleAuditExpression( propertyNameGetter, value, " like " );
return new SimpleAuditExpression( alias, propertyNameGetter, value, " like " );
}
/**
* Apply a "like" constraint
*/
public AuditCriterion like(String value, MatchMode matchMode) {
return new SimpleAuditExpression( propertyNameGetter, matchMode.toMatchString( value ), " like " );
return new SimpleAuditExpression( alias, propertyNameGetter, matchMode.toMatchString( value ), " like " );
}
/**
* Apply an "ilike" constraint
*/
public AuditCriterion ilike(T value) {
return new IlikeAuditExpression(propertyNameGetter, value.toString());
return new IlikeAuditExpression( alias, propertyNameGetter, value.toString() );
}
/**
* Apply an "ilike" constraint
*/
public AuditCriterion ilike(String value, MatchMode matchMode) {
return new IlikeAuditExpression( propertyNameGetter, matchMode.toMatchString( value ));
return new IlikeAuditExpression( alias, propertyNameGetter, matchMode.toMatchString( value ) );
}
/**
* Apply a "greater than" constraint
*/
public AuditCriterion gt(T value) {
return new SimpleAuditExpression( propertyNameGetter, value, ">" );
return new SimpleAuditExpression( alias, propertyNameGetter, value, ">" );
}
/**
* Apply a "less than" constraint
*/
public AuditCriterion lt(T value) {
return new SimpleAuditExpression( propertyNameGetter, value, "<" );
return new SimpleAuditExpression( alias, propertyNameGetter, value, "<" );
}
/**
* Apply a "less than or equal" constraint
*/
public AuditCriterion le(T value) {
return new SimpleAuditExpression( propertyNameGetter, value, "<=" );
return new SimpleAuditExpression( alias, propertyNameGetter, value, "<=" );
}
/**
* Apply a "greater than or equal" constraint
*/
public AuditCriterion ge(T value) {
return new SimpleAuditExpression( propertyNameGetter, value, ">=" );
return new SimpleAuditExpression( alias, propertyNameGetter, value, ">=" );
}
/**
* Apply a "between" constraint
*/
public AuditCriterion between(T lo, T hi) {
return new BetweenAuditExpression( propertyNameGetter, lo, hi );
return new BetweenAuditExpression( alias, propertyNameGetter, lo, hi );
}
/**
* Apply an "in" constraint
*/
public AuditCriterion in(T[] values) {
return new InAuditExpression( propertyNameGetter, values );
return new InAuditExpression( alias, propertyNameGetter, values );
}
/**
* Apply an "in" constraint
*/
public AuditCriterion in(Collection values) {
return new InAuditExpression( propertyNameGetter, values.toArray() );
return new InAuditExpression( alias, propertyNameGetter, values.toArray() );
}
/**
* Apply an "is null" constraint
*/
public AuditCriterion isNull() {
return new NullAuditExpression( propertyNameGetter );
return new NullAuditExpression( alias, propertyNameGetter );
}
/**
* Apply an "equal" constraint to another property
*/
public AuditCriterion eqProperty(String otherPropertyName) {
return new PropertyAuditExpression( propertyNameGetter, otherPropertyName, "=" );
/*
* We provide alias as otherAlias rather than null, because this seems the intuitive use case.
* E.g. if the user calls AuditEntity.property( "alias", "prop" ).eqProperty( "otherProp" )
* it is assumed that the otherProp is on the same entity as prop and therefore we have to use
* the same alias.
*/
return eqProperty( alias, otherPropertyName );
}
/**
* Apply an "equal" constraint to another property
*
* @param otherAlias the alias of the entity which owns the other property.
*/
public AuditCriterion eqProperty(String otherAlias, String otherPropertyName) {
return new PropertyAuditExpression( alias, propertyNameGetter, otherAlias, otherPropertyName, "=" );
}
/**
* Apply a "not equal" constraint to another property
*/
public AuditCriterion neProperty(String otherPropertyName) {
return new PropertyAuditExpression( propertyNameGetter, otherPropertyName, "<>" );
/*
* We provide alias as otherAlias rather than null, because this seems the intuitive use case.
* E.g. if the user calls AuditEntity.property( "alias", "prop" ).neProperty( "otherProp" )
* it is assumed that the otherProp is on the same entity as prop and therefore we have to use
* the same alias.
*/
return neProperty( alias, otherPropertyName );
}
/**
* Apply a "not equal" constraint to another property
*
* @param otherAlias the alias of the entity which owns the other property.
*/
public AuditCriterion neProperty(String otherAlias, String otherPropertyName) {
return new PropertyAuditExpression( alias, propertyNameGetter, otherAlias, otherPropertyName, "<>" );
}
/**
* Apply a "less than" constraint to another property
*/
public AuditCriterion ltProperty(String otherPropertyName) {
return new PropertyAuditExpression( propertyNameGetter, otherPropertyName, "<" );
/*
* We provide alias as otherAlias rather than null, because this seems the intuitive use case.
* E.g. if the user calls AuditEntity.property( "alias", "prop" ).ltProperty( "otherProp" )
* it is assumed that the otherProp is on the same entity as prop and therefore we have to use
* the same alias.
*/
return ltProperty( alias, otherPropertyName );
}
/**
* Apply a "less than" constraint to another property
*
* @param otherAlias the alias of the entity which owns the other property.
*/
public AuditCriterion ltProperty(String otherAlias, String otherPropertyName) {
return new PropertyAuditExpression( alias, propertyNameGetter, otherAlias, otherPropertyName, "<" );
}
/**
* Apply a "less than or equal" constraint to another property
*/
public AuditCriterion leProperty(String otherPropertyName) {
return new PropertyAuditExpression( propertyNameGetter, otherPropertyName, "<=" );
/*
* We provide alias as otherAlias rather than null, because this seems the intuitive use case.
* E.g. if the user calls AuditEntity.property( "alias", "prop" ).leProperty( "otherProp" )
* it is assumed that the otherProp is on the same entity as prop and therefore we have to use
* the same alias.
*/
return leProperty( alias, otherPropertyName );
}
/**
* Apply a "less than or equal" constraint to another property
*
* @param otherAlias the alias of the entity which owns the other property.
*/
public AuditCriterion leProperty(String otherAlias, String otherPropertyName) {
return new PropertyAuditExpression( alias, propertyNameGetter, otherAlias, otherPropertyName, "<=" );
}
/**
* Apply a "greater than" constraint to another property
*/
public AuditCriterion gtProperty(String otherPropertyName) {
return new PropertyAuditExpression( propertyNameGetter, otherPropertyName, ">" );
/*
* We provide alias as otherAlias rather than null, because this seems the intuitive use case.
* E.g. if the user calls AuditEntity.property( "alias", "prop" ).gtProperty( "otherProp" )
* it is assumed that the otherProp is on the same entity as prop and therefore we have to use
* the same alias.
*/
return gtProperty( alias, otherPropertyName );
}
/**
* Apply a "greater than" constraint to another property
*
* @param otherAlias the alias of the entity which owns the other property.
*/
public AuditCriterion gtProperty(String otherAlias, String otherPropertyName) {
return new PropertyAuditExpression( alias, propertyNameGetter, otherAlias, otherPropertyName, ">" );
}
/**
* Apply a "greater than or equal" constraint to another property
*/
public AuditCriterion geProperty(String otherPropertyName) {
return new PropertyAuditExpression( propertyNameGetter, otherPropertyName, ">=" );
/*
* We provide alias as otherAlias rather than null, because this seems the intuitive use case.
* E.g. if the user calls AuditEntity.property( "alias", "prop" ).geProperty( "otherProp" )
* it is assumed that the otherProp is on the same entity as prop and therefore we have to use
* the same alias.
*/
return geProperty( alias, otherPropertyName );
}
/**
* Apply a "greater than or equal" constraint to another property
*
* @param otherAlias the alias of the entity which owns the other property.
*/
public AuditCriterion geProperty(String otherAlias, String otherPropertyName) {
return new PropertyAuditExpression( alias, propertyNameGetter, otherAlias, otherPropertyName, ">=" );
}
/**
* Apply an "is not null" constraint to the another property
*/
public AuditCriterion isNotNull() {
return new NotNullAuditExpression( propertyNameGetter );
return new NotNullAuditExpression( alias, propertyNameGetter );
}
/**
@ -200,7 +291,7 @@ public class AuditProperty<T> implements AuditProjection {
* property
*/
public AggregatedAuditExpression maximize() {
return new AggregatedAuditExpression( propertyNameGetter, AggregatedAuditExpression.AggregatedMode.MAX );
return new AggregatedAuditExpression( alias, propertyNameGetter, AggregatedAuditExpression.AggregatedMode.MAX );
}
/**
@ -208,7 +299,7 @@ public class AuditProperty<T> implements AuditProjection {
* property
*/
public AggregatedAuditExpression minimize() {
return new AggregatedAuditExpression( propertyNameGetter, AggregatedAuditExpression.AggregatedMode.MIN );
return new AggregatedAuditExpression( alias, propertyNameGetter, AggregatedAuditExpression.AggregatedMode.MIN );
}
// Projections
@ -217,48 +308,48 @@ public class AuditProperty<T> implements AuditProjection {
* Projection on the maximum value
*/
public AuditProjection max() {
return new PropertyAuditProjection( propertyNameGetter, "max", false );
return new PropertyAuditProjection( alias, propertyNameGetter, "max", false );
}
/**
* Projection on the minimum value
*/
public AuditProjection min() {
return new PropertyAuditProjection( propertyNameGetter, "min", false );
return new PropertyAuditProjection( alias, propertyNameGetter, "min", false );
}
/**
* Projection counting the values
*/
public AuditProjection count() {
return new PropertyAuditProjection( propertyNameGetter, "count", false );
return new PropertyAuditProjection( alias, propertyNameGetter, "count", false );
}
/**
* Projection counting distinct values
*/
public AuditProjection countDistinct() {
return new PropertyAuditProjection( propertyNameGetter, "count", true );
return new PropertyAuditProjection( alias, propertyNameGetter, "count", true );
}
/**
* Projection on distinct values
*/
public AuditProjection distinct() {
return new PropertyAuditProjection( propertyNameGetter, null, true );
return new PropertyAuditProjection( alias, propertyNameGetter, null, true );
}
/**
* Projection using a custom function
*/
public AuditProjection function(String functionName) {
return new PropertyAuditProjection( propertyNameGetter, functionName, false );
return new PropertyAuditProjection( alias, propertyNameGetter, functionName, false );
}
// Projection on this property
public Triple<String, String, Boolean> getData(EnversService enversService) {
return Triple.make( null, propertyNameGetter.get( enversService ), false );
public ProjectionData getData(EnversService enversService) {
return new ProjectionData( null, alias, propertyNameGetter.get( enversService ), false );
}
// Order
@ -267,14 +358,14 @@ public class AuditProperty<T> implements AuditProjection {
* Sort the results by the property in ascending order
*/
public AuditOrder asc() {
return new PropertyAuditOrder( propertyNameGetter, true );
return new PropertyAuditOrder( alias, propertyNameGetter, true );
}
/**
* Sort the results by the property in descending order
*/
public AuditOrder desc() {
return new PropertyAuditOrder( propertyNameGetter, false );
return new PropertyAuditOrder( alias, propertyNameGetter, false );
}
@Override

View File

@ -17,9 +17,11 @@ import org.hibernate.envers.query.internal.property.PropertyNameGetter;
* @author Chris Cranford
*/
public class AuditRelatedId {
private final String alias;
private final PropertyNameGetter propertyNameGetter;
public AuditRelatedId(PropertyNameGetter propertyNameGetter) {
public AuditRelatedId(String alias, PropertyNameGetter propertyNameGetter) {
this.alias = alias;
this.propertyNameGetter = propertyNameGetter;
}
@ -30,7 +32,7 @@ public class AuditRelatedId {
* @return the criterion.
*/
public AuditCriterion eq(Object id) {
return new RelatedAuditEqualityExpression( propertyNameGetter, id, true );
return new RelatedAuditEqualityExpression( alias, propertyNameGetter, id, true );
}
/**
@ -40,7 +42,7 @@ public class AuditRelatedId {
* @return the criterion
*/
public AuditCriterion ne(Object id) {
return new RelatedAuditEqualityExpression( propertyNameGetter, id, false );
return new RelatedAuditEqualityExpression( alias, propertyNameGetter, id, false );
}
/**
@ -50,6 +52,6 @@ public class AuditRelatedId {
* @return the criterion
*/
public AuditCriterion in(Object[] values) {
return new RelatedAuditInExpression( propertyNameGetter, values );
return new RelatedAuditInExpression( alias, propertyNameGetter, values );
}
}

View File

@ -0,0 +1,59 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.envers.query.criteria.internal;
import java.util.Map;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.envers.internal.tools.query.QueryBuilder;
import org.hibernate.envers.query.criteria.AuditCriterion;
/**
* An abstract class for all expression types which are atomic (i.e. expressions
* which are not composed of one or more other expressions). For those expression
* types which base class already calculates the effective alias and resolves
* the corresponding entity name. The effect alias is either the alias that has been
* specified at creation time of this expression or if that alias is null, the base
* alias is used. This calculation is done in the
* {@link AuditCriterion#addToQuery(EnversService, AuditReaderImplementor, Map, String, QueryBuilder, Parameters)}
* implementation and then delegated for the concrete work to the template method
* {@link #addToQuery(EnversService, AuditReaderImplementor, String, String, QueryBuilder, Parameters)}.
*
* @author Felix Feisst (feisst dot felix at gmail dot com)
*/
abstract class AbstractAtomicExpression implements AuditCriterion {
private final String alias;
protected AbstractAtomicExpression(String alias) {
this.alias = alias;
}
@Override
public void addToQuery(
EnversService enversService,
AuditReaderImplementor versionsReader,
Map<String, String> aliasToEntityNameMap,
String baseAlias,
QueryBuilder qb,
Parameters parameters) {
final String effectiveAlias = alias == null ? baseAlias : alias;
final String entityName = aliasToEntityNameMap.get( effectiveAlias );
addToQuery(enversService, versionsReader, entityName, effectiveAlias, qb, parameters);
}
protected abstract void addToQuery(
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
String alias,
QueryBuilder qb,
Parameters parameters);
}

View File

@ -10,24 +10,25 @@ import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.envers.internal.tools.query.QueryBuilder;
import org.hibernate.envers.query.criteria.AuditCriterion;
import org.hibernate.envers.query.internal.property.PropertyNameGetter;
/**
* @author Adam Warski (adam at warski dot org)
*/
public class BetweenAuditExpression implements AuditCriterion {
public class BetweenAuditExpression extends AbstractAtomicExpression {
private PropertyNameGetter propertyNameGetter;
private Object lo;
private Object hi;
public BetweenAuditExpression(PropertyNameGetter propertyNameGetter, Object lo, Object hi) {
public BetweenAuditExpression(String alias, PropertyNameGetter propertyNameGetter, Object lo, Object hi) {
super( alias );
this.propertyNameGetter = propertyNameGetter;
this.lo = lo;
this.hi = hi;
}
public void addToQuery(
@Override
protected void addToQuery(
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
@ -43,7 +44,7 @@ public class BetweenAuditExpression implements AuditCriterion {
CriteriaTools.checkPropertyNotARelation( enversService, entityName, propertyName );
Parameters subParams = parameters.addSubParameters( Parameters.AND );
subParams.addWhereWithParam( propertyName, ">=", lo );
subParams.addWhereWithParam( propertyName, "<=", hi );
subParams.addWhereWithParam( alias, propertyName, ">=", lo );
subParams.addWhereWithParam( alias, propertyName, "<=", hi );
}
}

View File

@ -10,32 +10,33 @@ import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.envers.internal.tools.query.QueryBuilder;
import org.hibernate.envers.query.criteria.AuditCriterion;
/**
* A criterion that expresses that the id of an entity is equal or not equal to some specified value.
*
* @author Adam Warski (adam at warski dot org)
*/
public class IdentifierEqAuditExpression implements AuditCriterion {
public class IdentifierEqAuditExpression extends AbstractAtomicExpression {
private final Object id;
private final boolean equals;
public IdentifierEqAuditExpression(Object id, boolean equals) {
public IdentifierEqAuditExpression(String alias, Object id, boolean equals) {
super( alias );
this.id = id;
this.equals = equals;
}
@Override
public void addToQuery(
protected void addToQuery(
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
String alias,
QueryBuilder qb,
Parameters parameters) {
String prefix = enversService.getAuditEntitiesConfiguration().getOriginalIdPropName();
enversService.getEntitiesConfigurations().get( entityName )
.getIdMapper()
.addIdEqualsToQuery( parameters, id, enversService.getAuditEntitiesConfiguration().getOriginalIdPropName(), equals );
.addIdEqualsToQuery( parameters, id, alias, prefix, equals );
}
}

View File

@ -11,20 +11,21 @@ import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.envers.internal.tools.query.QueryBuilder;
import org.hibernate.envers.query.criteria.AuditCriterion;
import org.hibernate.envers.query.internal.property.PropertyNameGetter;
public class IlikeAuditExpression implements AuditCriterion {
public class IlikeAuditExpression extends AbstractAtomicExpression {
private PropertyNameGetter propertyNameGetter;
private String value;
public IlikeAuditExpression(PropertyNameGetter propertyNameGetter, String value) {
public IlikeAuditExpression(String alias, PropertyNameGetter propertyNameGetter, String value) {
super( alias );
this.propertyNameGetter = propertyNameGetter;
this.value = value;
}
public void addToQuery(
@Override
protected void addToQuery(
EnversService enversService,
AuditReaderImplementor versionsReader, String entityName,
String alias, QueryBuilder qb, Parameters parameters) {
@ -37,7 +38,7 @@ public class IlikeAuditExpression implements AuditCriterion {
);
CriteriaTools.checkPropertyNotARelation( enversService, entityName, propertyName );
parameters.addWhereWithFunction( propertyName, " lower ", " like ", value.toLowerCase( Locale.ROOT ) );
parameters.addWhereWithFunction( alias, propertyName, " lower ", " like ", value.toLowerCase( Locale.ROOT ) );
}
}

View File

@ -10,22 +10,23 @@ import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.envers.internal.tools.query.QueryBuilder;
import org.hibernate.envers.query.criteria.AuditCriterion;
import org.hibernate.envers.query.internal.property.PropertyNameGetter;
/**
* @author Adam Warski (adam at warski dot org)
*/
public class InAuditExpression implements AuditCriterion {
public class InAuditExpression extends AbstractAtomicExpression {
private PropertyNameGetter propertyNameGetter;
private Object[] values;
public InAuditExpression(PropertyNameGetter propertyNameGetter, Object[] values) {
public InAuditExpression(String alias, PropertyNameGetter propertyNameGetter, Object[] values) {
super( alias );
this.propertyNameGetter = propertyNameGetter;
this.values = values;
}
public void addToQuery(
@Override
protected void addToQuery(
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
@ -39,6 +40,6 @@ public class InAuditExpression implements AuditCriterion {
propertyNameGetter
);
CriteriaTools.checkPropertyNotARelation( enversService, entityName, propertyName );
parameters.addWhereWithParams( propertyName, "in (", values, ")" );
parameters.addWhereWithParams( alias, propertyName, "in (", values, ")" );
}
}

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.envers.query.criteria.internal;
import java.util.Map;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.Parameters;
@ -29,13 +31,13 @@ public class LogicalAuditExpression implements AuditCriterion {
public void addToQuery(
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
Map<String, String> aliasToEntityNameMap,
String alias,
QueryBuilder qb,
Parameters parameters) {
Parameters opParameters = parameters.addSubParameters( op );
lhs.addToQuery( enversService, versionsReader, entityName, alias, qb, opParameters.addSubParameters( "and" ) );
rhs.addToQuery( enversService, versionsReader, entityName, alias, qb, opParameters.addSubParameters( "and" ) );
lhs.addToQuery( enversService, versionsReader, aliasToEntityNameMap, alias, qb, opParameters.addSubParameters( "and" ) );
rhs.addToQuery( enversService, versionsReader, aliasToEntityNameMap, alias, qb, opParameters.addSubParameters( "and" ) );
}
}

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.envers.query.criteria.internal;
import java.util.Map;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.Parameters;
@ -25,10 +27,10 @@ public class NotAuditExpression implements AuditCriterion {
public void addToQuery(
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
Map<String, String> aliasToEntityNameMap,
String alias,
QueryBuilder qb,
Parameters parameters) {
criterion.addToQuery( enversService, versionsReader, entityName, alias, qb, parameters.addNegatedParameters() );
criterion.addToQuery( enversService, versionsReader, aliasToEntityNameMap, alias, qb, parameters.addNegatedParameters() );
}
}

View File

@ -11,20 +11,21 @@ import org.hibernate.envers.internal.entities.RelationDescription;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.envers.internal.tools.query.QueryBuilder;
import org.hibernate.envers.query.criteria.AuditCriterion;
import org.hibernate.envers.query.internal.property.PropertyNameGetter;
/**
* @author Adam Warski (adam at warski dot org)
*/
public class NotNullAuditExpression implements AuditCriterion {
public class NotNullAuditExpression extends AbstractAtomicExpression {
private PropertyNameGetter propertyNameGetter;
public NotNullAuditExpression(PropertyNameGetter propertyNameGetter) {
public NotNullAuditExpression(String alias, PropertyNameGetter propertyNameGetter) {
super( alias );
this.propertyNameGetter = propertyNameGetter;
}
public void addToQuery(
@Override
protected void addToQuery(
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
@ -40,10 +41,10 @@ public class NotNullAuditExpression implements AuditCriterion {
RelationDescription relatedEntity = CriteriaTools.getRelatedEntity( enversService, entityName, propertyName );
if ( relatedEntity == null ) {
parameters.addNotNullRestriction( propertyName, true );
parameters.addNotNullRestriction( alias, propertyName );
}
else {
relatedEntity.getIdMapper().addIdEqualsToQuery( parameters, null, null, false );
relatedEntity.getIdMapper().addIdEqualsToQuery( parameters, null, alias, null, false );
}
}
}

View File

@ -11,20 +11,21 @@ import org.hibernate.envers.internal.entities.RelationDescription;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.envers.internal.tools.query.QueryBuilder;
import org.hibernate.envers.query.criteria.AuditCriterion;
import org.hibernate.envers.query.internal.property.PropertyNameGetter;
/**
* @author Adam Warski (adam at warski dot org)
*/
public class NullAuditExpression implements AuditCriterion {
public class NullAuditExpression extends AbstractAtomicExpression {
private PropertyNameGetter propertyNameGetter;
public NullAuditExpression(PropertyNameGetter propertyNameGetter) {
public NullAuditExpression(String alias, PropertyNameGetter propertyNameGetter) {
super( alias );
this.propertyNameGetter = propertyNameGetter;
}
public void addToQuery(
@Override
protected void addToQuery(
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
@ -40,10 +41,10 @@ public class NullAuditExpression implements AuditCriterion {
RelationDescription relatedEntity = CriteriaTools.getRelatedEntity( enversService, entityName, propertyName );
if ( relatedEntity == null ) {
parameters.addNullRestriction( propertyName, true );
parameters.addNullRestriction( alias, propertyName );
}
else {
relatedEntity.getIdMapper().addIdEqualsToQuery( parameters, null, null, true );
relatedEntity.getIdMapper().addIdEqualsToQuery( parameters, null, alias, null, true );
}
}
}

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.envers.query.criteria.internal;
import java.util.Map;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.Parameters;
@ -17,23 +19,38 @@ import org.hibernate.envers.query.internal.property.PropertyNameGetter;
* @author Adam Warski (adam at warski dot org)
*/
public class PropertyAuditExpression implements AuditCriterion {
private String alias;
private PropertyNameGetter propertyNameGetter;
private String otherAlias;
private String otherPropertyName;
private String op;
public PropertyAuditExpression(PropertyNameGetter propertyNameGetter, String otherPropertyName, String op) {
public PropertyAuditExpression(
String alias,
PropertyNameGetter propertyNameGetter,
String otherAlias,
String otherPropertyName,
String op
) {
this.alias = alias;
this.propertyNameGetter = propertyNameGetter;
this.otherAlias = otherAlias;
this.otherPropertyName = otherPropertyName;
this.op = op;
}
@Override
public void addToQuery(
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
String alias,
Map<String, String> aliasToEntityNameMap,
String baseAlias,
QueryBuilder qb,
Parameters parameters) {
String effectiveAlias = alias == null ? baseAlias : alias;
String effectiveOtherAlias = otherAlias == null ? baseAlias : otherAlias;
String entityName = aliasToEntityNameMap.get( effectiveAlias );
String otherEntityName = aliasToEntityNameMap.get( effectiveOtherAlias );
String propertyName = CriteriaTools.determinePropertyName(
enversService,
versionsReader,
@ -41,7 +58,14 @@ public class PropertyAuditExpression implements AuditCriterion {
propertyNameGetter
);
CriteriaTools.checkPropertyNotARelation( enversService, entityName, propertyName );
CriteriaTools.checkPropertyNotARelation( enversService, entityName, otherPropertyName );
parameters.addWhere( propertyName, op, otherPropertyName );
/*
* Check that the other property name is not a relation. However, we can only
* do this for audited entities. If the other property belongs to a non-audited
* entity, we have to skip this check.
*/
if ( enversService.getEntitiesConfigurations().isVersioned( otherEntityName ) ) {
CriteriaTools.checkPropertyNotARelation( enversService, otherEntityName, otherPropertyName );
}
parameters.addWhere( effectiveAlias, propertyName, op, effectiveOtherAlias, otherPropertyName );
}
}

View File

@ -19,19 +19,20 @@ import org.hibernate.envers.query.internal.property.PropertyNameGetter;
* @author Chris Cranford
* @since 5.2
*/
public class RelatedAuditEqualityExpression implements AuditCriterion {
public class RelatedAuditEqualityExpression extends AbstractAtomicExpression {
private final PropertyNameGetter propertyNameGetter;
private final Object id;
private final boolean equals;
public RelatedAuditEqualityExpression(PropertyNameGetter propertyNameGetter, Object id, boolean equals) {
public RelatedAuditEqualityExpression(String alias, PropertyNameGetter propertyNameGetter, Object id, boolean equals) {
super( alias );
this.propertyNameGetter = propertyNameGetter;
this.id = id;
this.equals = equals;
}
@Override
public void addToQuery(
protected void addToQuery(
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
@ -51,6 +52,6 @@ public class RelatedAuditEqualityExpression implements AuditCriterion {
"This criterion can only be used on a property that is a relation to another property."
);
}
relatedEntity.getIdMapper().addIdEqualsToQuery( parameters, id, null, equals );
relatedEntity.getIdMapper().addIdEqualsToQuery( parameters, id, alias, null, equals );
}
}

View File

@ -22,17 +22,20 @@ import org.hibernate.envers.query.internal.property.PropertyNameGetter;
* @author Chris Cranford
* @since 5.2
*/
public class RelatedAuditInExpression implements AuditCriterion {
public class RelatedAuditInExpression extends AbstractAtomicExpression {
private final PropertyNameGetter propertyNameGetter;
private final Object[] ids;
public RelatedAuditInExpression(PropertyNameGetter propertyNameGetter, Object[] ids) {
public RelatedAuditInExpression(String alias, PropertyNameGetter propertyNameGetter, Object[] ids) {
super( alias );
this.propertyNameGetter = propertyNameGetter;
this.ids = ids;
}
public void addToQuery(EnversService enversService,
@Override
protected void addToQuery(
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
String alias,
@ -56,7 +59,7 @@ public class RelatedAuditInExpression implements AuditCriterion {
List<QueryParameterData> qpdList = relatedEntity.getIdMapper().mapToQueryParametersFromId( propertyName );
if ( qpdList != null ) {
QueryParameterData qpd = qpdList.iterator().next();
parameters.addWhereWithParams( qpd.getQueryParameterName(), "in (", ids, ")" );
parameters.addWhereWithParams( alias, qpd.getQueryParameterName(), "in (", ids, ")" );
}
}
}

View File

@ -10,28 +10,28 @@ import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.envers.internal.tools.query.QueryBuilder;
import org.hibernate.envers.query.criteria.AuditCriterion;
/**
* @author Adam Warski (adam at warski dot org)
*/
public class RevisionTypeAuditExpression implements AuditCriterion {
public class RevisionTypeAuditExpression extends AbstractAtomicExpression {
private Object value;
private String op;
public RevisionTypeAuditExpression(Object value, String op) {
public RevisionTypeAuditExpression(String alias, Object value, String op) {
super( alias );
this.value = value;
this.op = op;
}
@Override
public void addToQuery(
protected void addToQuery(
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
String alias,
QueryBuilder qb,
Parameters parameters) {
parameters.addWhereWithParam( enversService.getAuditEntitiesConfiguration().getRevisionTypePropName(), op, value );
parameters.addWhereWithParam( alias, enversService.getAuditEntitiesConfiguration().getRevisionTypePropName(), op, value );
}
}

View File

@ -13,7 +13,6 @@ import org.hibernate.envers.internal.entities.RelationDescription;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.envers.internal.tools.query.QueryBuilder;
import org.hibernate.envers.query.criteria.AuditCriterion;
import org.hibernate.envers.query.internal.property.PropertyNameGetter;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.ComponentType;
@ -22,18 +21,20 @@ import org.hibernate.type.Type;
/**
* @author Adam Warski (adam at warski dot org)
*/
public class SimpleAuditExpression implements AuditCriterion {
public class SimpleAuditExpression extends AbstractAtomicExpression {
private PropertyNameGetter propertyNameGetter;
private Object value;
private String op;
public SimpleAuditExpression(PropertyNameGetter propertyNameGetter, Object value, String op) {
public SimpleAuditExpression(String alias, PropertyNameGetter propertyNameGetter, Object value, String op) {
super( alias );
this.propertyNameGetter = propertyNameGetter;
this.value = value;
this.op = op;
}
public void addToQuery(
@Override
protected void addToQuery(
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
@ -64,6 +65,7 @@ public class SimpleAuditExpression implements AuditCriterion {
for ( int i = 0; i < componentType.getPropertyNames().length; i++ ) {
final Object componentValue = componentType.getPropertyValue( value, i, session );
parameters.addWhereWithParam(
alias,
propertyName + "_" + componentType.getPropertyNames()[ i ],
op,
componentValue
@ -71,7 +73,7 @@ public class SimpleAuditExpression implements AuditCriterion {
}
}
else {
parameters.addWhereWithParam( propertyName, op, value );
parameters.addWhereWithParam( alias, propertyName, op, value );
}
}
else {
@ -82,7 +84,7 @@ public class SimpleAuditExpression implements AuditCriterion {
);
}
Object id = relatedEntity.getIdMapper().mapToIdFromEntity( value );
relatedEntity.getIdMapper().addIdEqualsToQuery( parameters, id, null, "=".equals( op ) );
relatedEntity.getIdMapper().addIdEqualsToQuery( parameters, id, alias, null, "=".equals( op ) );
}
}

View File

@ -23,7 +23,6 @@ import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.exception.AuditException;
import org.hibernate.envers.internal.entities.EntityInstantiator;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.Triple;
import org.hibernate.envers.internal.tools.query.QueryBuilder;
import org.hibernate.envers.query.AuditAssociationQuery;
import org.hibernate.envers.query.AuditQuery;
@ -48,6 +47,7 @@ public abstract class AbstractAuditQuery implements AuditQueryImplementor {
protected String entityClassName;
protected String versionsEntityName;
protected QueryBuilder qb;
protected final Map<String, String> aliasToEntityNameMap = new HashMap<>();
protected boolean hasOrder;
@ -79,10 +79,16 @@ public abstract class AbstractAuditQuery implements AuditQueryImplementor {
entityClassName = cls.getName();
this.entityName = entityName;
versionsEntityName = enversService.getAuditEntitiesConfiguration().getAuditEntityName( entityName );
aliasToEntityNameMap.put( REFERENCED_ENTITY_ALIAS, entityName );
qb = new QueryBuilder( versionsEntityName, REFERENCED_ENTITY_ALIAS );
}
@Override
public String getAlias() {
return REFERENCED_ENTITY_ALIAS;
}
protected Query buildQuery() {
Query query = qb.toQuery( versionsReader.getSession() );
setQueryProperties( query );
@ -123,15 +129,22 @@ public abstract class AbstractAuditQuery implements AuditQueryImplementor {
// Projection and order
public AuditQuery addProjection(AuditProjection projection) {
Triple<String, String, Boolean> projectionData = projection.getData( enversService );
registerProjection( entityName, projection );
AuditProjection.ProjectionData projectionData = projection.getData( enversService );
String projectionEntityAlias = projectionData.getAlias( REFERENCED_ENTITY_ALIAS );
String projectionEntityName = aliasToEntityNameMap.get( projectionEntityAlias );
registerProjection( projectionEntityName, projection );
String propertyName = CriteriaTools.determinePropertyName(
enversService,
versionsReader,
entityName,
projectionData.getSecond()
projectionEntityName,
projectionData.getPropertyName()
);
qb.addProjection(
projectionData.getFunction(),
projectionEntityAlias,
propertyName,
projectionData.isDistinct()
);
qb.addProjection( projectionData.getFirst(), REFERENCED_ENTITY_ALIAS, propertyName, projectionData.getThird() );
return this;
}
@ -146,22 +159,43 @@ public abstract class AbstractAuditQuery implements AuditQueryImplementor {
public AuditQuery addOrder(AuditOrder order) {
hasOrder = true;
Pair<String, Boolean> orderData = order.getData( enversService );
AuditOrder.OrderData orderData = order.getData( enversService );
String orderEntityAlias = orderData.getAlias( REFERENCED_ENTITY_ALIAS );
String orderEntityName = aliasToEntityNameMap.get( orderEntityAlias );
String propertyName = CriteriaTools.determinePropertyName(
enversService,
versionsReader,
entityName,
orderData.getFirst()
orderEntityName,
orderData.getPropertyName()
);
qb.addOrder( REFERENCED_ENTITY_ALIAS, propertyName, orderData.getSecond() );
qb.addOrder( orderEntityAlias, propertyName, orderData.isAscending() );
return this;
}
@Override
public AuditAssociationQuery<? extends AuditQuery> traverseRelation(String associationName, JoinType joinType) {
return traverseRelation(
associationName,
joinType,
null
);
}
@Override
public AuditAssociationQuery<? extends AuditQuery> traverseRelation(String associationName, JoinType joinType, String alias) {
AuditAssociationQueryImpl<AuditQueryImplementor> result = associationQueryMap.get( associationName );
if (result == null) {
result = new AuditAssociationQueryImpl<>( enversService, versionsReader, this, qb, entityName, associationName, joinType, REFERENCED_ENTITY_ALIAS );
result = new AuditAssociationQueryImpl<>(
enversService,
versionsReader,
this,
qb,
associationName,
joinType,
aliasToEntityNameMap,
REFERENCED_ENTITY_ALIAS,
alias
);
associationQueries.add( result );
associationQueryMap.put( associationName, result );
}

View File

@ -25,7 +25,6 @@ import org.hibernate.envers.internal.entities.RelationDescription;
import org.hibernate.envers.internal.entities.mapper.id.IdMapper;
import org.hibernate.envers.internal.entities.mapper.relation.MiddleIdData;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.Triple;
import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.envers.internal.tools.query.QueryBuilder;
import org.hibernate.envers.query.AuditAssociationQuery;
@ -33,7 +32,6 @@ import org.hibernate.envers.query.criteria.AuditCriterion;
import org.hibernate.envers.query.criteria.internal.CriteriaTools;
import org.hibernate.envers.query.order.AuditOrder;
import org.hibernate.envers.query.projection.AuditProjection;
import org.hibernate.envers.tools.Pair;
/**
* @author Felix Feisst (feisst dot felix at gmail dot com)
@ -51,28 +49,29 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
private final IdMapper ownerAssociationIdMapper;
private final String ownerAlias;
private final String alias;
private final Map<String, String> aliasToEntityNameMap;
private final List<AuditCriterion> criterions = new ArrayList<>();
private final Parameters parameters;
private final List<AuditAssociationQueryImpl<?>> associationQueries = new ArrayList<>();
private final Map<String, AuditAssociationQueryImpl<AuditAssociationQueryImpl<Q>>> associationQueryMap = new HashMap<>();
private boolean hasProjections;
private boolean hasOrders;
public AuditAssociationQueryImpl(
final EnversService enversService,
final AuditReaderImplementor auditReader,
final Q parent,
final QueryBuilder queryBuilder,
final String ownerEntityName,
final String propertyName,
final JoinType joinType,
final String ownerAlias) {
final Map<String, String> aliasToEntityNameMap,
final String ownerAlias,
final String userSuppliedAlias) {
this.enversService = enversService;
this.auditReader = auditReader;
this.parent = parent;
this.queryBuilder = queryBuilder;
this.joinType = joinType;
String ownerEntityName = aliasToEntityNameMap.get( ownerAlias );
final RelationDescription relationDescription = CriteriaTools.getRelatedEntity(
enversService,
ownerEntityName,
@ -84,8 +83,15 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
this.entityName = relationDescription.getToEntityName();
this.ownerAssociationIdMapper = relationDescription.getIdMapper();
this.ownerAlias = ownerAlias;
alias = queryBuilder.generateAlias();
parameters = queryBuilder.addParameters( alias );
this.alias = userSuppliedAlias == null ? queryBuilder.generateAlias() : userSuppliedAlias;
aliasToEntityNameMap.put( this.alias, entityName );
this.aliasToEntityNameMap = aliasToEntityNameMap;
parameters = queryBuilder.addParameters( this.alias );
}
@Override
public String getAlias() {
return alias;
}
@Override
@ -102,6 +108,18 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
public AuditAssociationQueryImpl<AuditAssociationQueryImpl<Q>> traverseRelation(
String associationName,
JoinType joinType) {
return traverseRelation(
associationName,
joinType,
null
);
}
@Override
public AuditAssociationQueryImpl<AuditAssociationQueryImpl<Q>> traverseRelation(
String associationName,
JoinType joinType,
String alias) {
AuditAssociationQueryImpl<AuditAssociationQueryImpl<Q>> result = associationQueryMap.get( associationName );
if ( result == null ) {
result = new AuditAssociationQueryImpl<>(
@ -109,9 +127,10 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
auditReader,
this,
queryBuilder,
entityName,
associationName,
joinType,
aliasToEntityNameMap,
this.alias,
alias
);
associationQueries.add( result );
@ -128,30 +147,37 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
@Override
public AuditAssociationQueryImpl<Q> addProjection(AuditProjection projection) {
hasProjections = true;
Triple<String, String, Boolean> projectionData = projection.getData( enversService );
AuditProjection.ProjectionData projectionData = projection.getData( enversService );
String projectionEntityAlias = projectionData.getAlias( alias );
String projectionEntityName = aliasToEntityNameMap.get( projectionEntityAlias );
String propertyName = CriteriaTools.determinePropertyName(
enversService,
auditReader,
entityName,
projectionData.getSecond()
projectionEntityName,
projectionData.getPropertyName()
);
queryBuilder.addProjection( projectionData.getFirst(), alias, propertyName, projectionData.getThird() );
registerProjection( entityName, projection );
queryBuilder.addProjection(
projectionData.getFunction(),
projectionEntityAlias,
propertyName,
projectionData.isDistinct()
);
registerProjection( projectionEntityName, projection );
return this;
}
@Override
public AuditAssociationQueryImpl<Q> addOrder(AuditOrder order) {
hasOrders = true;
Pair<String, Boolean> orderData = order.getData( enversService );
AuditOrder.OrderData orderData = order.getData( enversService );
String orderEntityAlias = orderData.getAlias( alias );
String orderEntityName = aliasToEntityNameMap.get( orderEntityAlias );
String propertyName = CriteriaTools.determinePropertyName(
enversService,
auditReader,
entityName,
orderData.getFirst()
orderEntityName,
orderData.getPropertyName()
);
queryBuilder.addOrder( alias, propertyName, orderData.getSecond() );
queryBuilder.addOrder( orderEntityAlias, propertyName, orderData.isAscending() );
return this;
}
@ -213,108 +239,80 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
return parent;
}
protected boolean hasCriterions() {
boolean result = !criterions.isEmpty();
if ( !result ) {
for ( final AuditAssociationQueryImpl<?> sub : associationQueries ) {
if ( sub.hasCriterions() ) {
result = true;
break;
}
}
}
return result;
}
protected boolean hasOrders() {
boolean result = hasOrders;
if ( !result ) {
for ( final AuditAssociationQueryImpl<?> sub : associationQueries ) {
if ( sub.hasOrders() ) {
result = true;
break;
}
}
}
return result;
}
protected boolean hasProjections() {
boolean result = hasProjections;
if ( !result ) {
for ( final AuditAssociationQueryImpl<?> sub : associationQueries ) {
if ( sub.hasProjections() ) {
result = true;
break;
}
}
}
return result;
}
protected void addCriterionsToQuery(AuditReaderImplementor versionsReader) {
if ( hasCriterions() || hasOrders() || hasProjections() ) {
if ( enversService.getEntitiesConfigurations().isVersioned( entityName ) ) {
String auditEntityName = enversService.getAuditEntitiesConfiguration().getAuditEntityName( entityName );
queryBuilder.addFrom( auditEntityName, alias, false );
if ( enversService.getEntitiesConfigurations().isVersioned( entityName ) ) {
String auditEntityName = enversService.getAuditEntitiesConfiguration().getAuditEntityName( entityName );
Parameters joinConditionParameters = queryBuilder.addJoin( joinType, auditEntityName, alias, false );
// owner.reference_id = target.originalId.id
AuditEntitiesConfiguration verEntCfg = enversService.getAuditEntitiesConfiguration();
String originalIdPropertyName = verEntCfg.getOriginalIdPropName();
IdMapper idMapperTarget = enversService.getEntitiesConfigurations().get( entityName ).getIdMapper();
final String prefix = alias.concat( "." ).concat( originalIdPropertyName );
ownerAssociationIdMapper.addIdsEqualToQuery(
queryBuilder.getRootParameters(),
ownerAlias,
idMapperTarget,
prefix
);
// owner.reference_id = target.originalId.id
AuditEntitiesConfiguration verEntCfg = enversService.getAuditEntitiesConfiguration();
String originalIdPropertyName = verEntCfg.getOriginalIdPropName();
IdMapper idMapperTarget = enversService.getEntitiesConfigurations().get( entityName ).getIdMapper();
final String prefix = alias.concat( "." ).concat( originalIdPropertyName );
ownerAssociationIdMapper.addIdsEqualToQuery(
joinConditionParameters,
ownerAlias,
idMapperTarget,
prefix
);
// filter reference of target entity
String revisionPropertyPath = verEntCfg.getRevisionNumberPath();
MiddleIdData referencedIdData = new MiddleIdData(
verEntCfg,
enversService.getEntitiesConfigurations().get( entityName ).getIdMappingData(),
null,
entityName,
enversService.getEntitiesConfigurations().isVersioned( entityName )
);
enversService.getAuditStrategy().addEntityAtRevisionRestriction(
enversService.getGlobalConfiguration(),
queryBuilder,
parameters,
revisionPropertyPath,
verEntCfg.getRevisionEndFieldName(),
true,
referencedIdData,
revisionPropertyPath,
originalIdPropertyName,
alias,
queryBuilder.generateAlias(),
true
);
}
else {
queryBuilder.addFrom( entityName, alias, false );
// owner.reference_id = target.id
final IdMapper idMapperTarget = enversService.getEntitiesConfigurations()
.getNotVersionEntityConfiguration( entityName )
.getIdMapper();
ownerAssociationIdMapper.addIdsEqualToQuery(
queryBuilder.getRootParameters(),
ownerAlias,
idMapperTarget,
alias
);
// filter revision of target entity
Parameters parametersToUse = parameters;
String revisionPropertyPath = verEntCfg.getRevisionNumberPath();
if (joinType == JoinType.LEFT) {
parametersToUse = parameters.addSubParameters( Parameters.OR );
parametersToUse.addNullRestriction( revisionPropertyPath, true );
parametersToUse = parametersToUse.addSubParameters( Parameters.AND );
}
MiddleIdData referencedIdData = new MiddleIdData(
verEntCfg,
enversService.getEntitiesConfigurations().get( entityName ).getIdMappingData(),
null,
entityName,
enversService.getEntitiesConfigurations().isVersioned( entityName )
);
enversService.getAuditStrategy().addEntityAtRevisionRestriction(
enversService.getGlobalConfiguration(),
queryBuilder,
parametersToUse,
revisionPropertyPath,
verEntCfg.getRevisionEndFieldName(),
true,
referencedIdData,
revisionPropertyPath,
originalIdPropertyName,
alias,
queryBuilder.generateAlias(),
true
);
}
else {
Parameters joinConditionParameters = queryBuilder.addJoin( joinType, entityName, alias, false );
// owner.reference_id = target.id
final IdMapper idMapperTarget = enversService.getEntitiesConfigurations()
.getNotVersionEntityConfiguration( entityName )
.getIdMapper();
ownerAssociationIdMapper.addIdsEqualToQuery(
joinConditionParameters,
ownerAlias,
idMapperTarget,
alias
);
}
for ( AuditCriterion criterion : criterions ) {
criterion.addToQuery( enversService, versionsReader, entityName, alias, queryBuilder, parameters );
}
for ( AuditCriterion criterion : criterions ) {
criterion.addToQuery(
enversService,
versionsReader,
aliasToEntityNameMap,
alias,
queryBuilder,
parameters
);
}
for ( final AuditAssociationQueryImpl<?> sub : associationQueries ) {
sub.addCriterionsToQuery( versionsReader );
}
for ( final AuditAssociationQueryImpl<?> sub : associationQueries ) {
sub.addCriterionsToQuery( versionsReader );
}
}

View File

@ -107,7 +107,7 @@ public class EntitiesAtRevisionQuery extends AbstractAuditQuery {
criterion.addToQuery(
enversService,
versionsReader,
entityName,
aliasToEntityNameMap,
QueryConstants.REFERENCED_ENTITY_ALIAS,
qb,
qb.getRootParameters()

View File

@ -45,7 +45,6 @@ public class EntitiesModifiedAtRevisionQuery extends AbstractAuditQuery {
}
@Override
@SuppressWarnings({"unchecked"})
public List list() {
/*
* The query that we need to create:
@ -63,7 +62,7 @@ public class EntitiesModifiedAtRevisionQuery extends AbstractAuditQuery {
criterion.addToQuery(
enversService,
versionsReader,
entityName,
aliasToEntityNameMap,
QueryConstants.REFERENCED_ENTITY_ALIAS,
qb,
qb.getRootParameters()

View File

@ -94,7 +94,7 @@ public class RevisionsOfEntityQuery extends AbstractAuditQuery {
criterion.addToQuery(
enversService,
versionsReader,
entityName,
aliasToEntityNameMap,
QueryConstants.REFERENCED_ENTITY_ALIAS,
qb,
qb.getRootParameters()

View File

@ -7,7 +7,6 @@
package org.hibernate.envers.query.order;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.tools.Pair;
/**
* @author Adam Warski (adam at warski dot org)
@ -16,7 +15,34 @@ public interface AuditOrder {
/**
* @param enversService The EnversService
*
* @return A pair: (property name, ascending?).
* @return the order data.
*/
Pair<String, Boolean> getData(EnversService enversService);
OrderData getData(EnversService enversService);
class OrderData {
private final String alias;
private final String propertyName;
private final boolean ascending;
public OrderData(String alias, String propertyName, boolean ascending) {
this.alias = alias;
this.propertyName = propertyName;
this.ascending = ascending;
}
public String getAlias(String baseAlias) {
return alias == null ? baseAlias : alias;
}
public String getPropertyName() {
return propertyName;
}
public boolean isAscending() {
return ascending;
}
}
}

View File

@ -9,22 +9,23 @@ package org.hibernate.envers.query.order.internal;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.query.internal.property.PropertyNameGetter;
import org.hibernate.envers.query.order.AuditOrder;
import org.hibernate.envers.tools.Pair;
/**
* @author Adam Warski (adam at warski dot org)
*/
public class PropertyAuditOrder implements AuditOrder {
private final String alias;
private final PropertyNameGetter propertyNameGetter;
private final boolean asc;
public PropertyAuditOrder(PropertyNameGetter propertyNameGetter, boolean asc) {
public PropertyAuditOrder(String alias, PropertyNameGetter propertyNameGetter, boolean asc) {
this.alias = alias;
this.propertyNameGetter = propertyNameGetter;
this.asc = asc;
}
@Override
public Pair<String, Boolean> getData(EnversService enversService) {
return Pair.make( propertyNameGetter.get( enversService ), asc );
public OrderData getData(EnversService enversService) {
return new OrderData( alias, propertyNameGetter.get( enversService ), asc );
}
}

View File

@ -8,7 +8,6 @@ package org.hibernate.envers.query.projection;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.internal.entities.EntityInstantiator;
import org.hibernate.envers.internal.tools.Triple;
/**
* @author Adam Warski (adam at warski dot org)
@ -17,9 +16,9 @@ public interface AuditProjection {
/**
* @param enversService The EnversService
*
* @return A triple: (function name - possibly null, property name, add distinct?).
* @return the projection data
*/
Triple<String, String, Boolean> getData(EnversService enversService);
ProjectionData getData(EnversService enversService);
/**
* @param enversService the Envers service
@ -36,4 +35,36 @@ public interface AuditProjection {
final Number revision,
final Object value
);
class ProjectionData {
private final String function;
private final String alias;
private final String propertyName;
private final boolean distinct;
public ProjectionData(String function, String alias, String propertyName, boolean distinct) {
this.function = function;
this.alias = alias;
this.propertyName = propertyName;
this.distinct = distinct;
}
public String getFunction() {
return function;
}
public String getAlias(String baseAlias) {
return alias == null ? baseAlias : alias;
}
public String getPropertyName() {
return propertyName;
}
public boolean isDistinct() {
return distinct;
}
}
}

View File

@ -10,7 +10,6 @@ import java.util.Map;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.internal.entities.EntityInstantiator;
import org.hibernate.envers.internal.tools.Triple;
import org.hibernate.envers.query.projection.AuditProjection;
/**
@ -18,16 +17,18 @@ import org.hibernate.envers.query.projection.AuditProjection;
*/
public class EntityAuditProjection implements AuditProjection {
private final String alias;
private final boolean distinct;
public EntityAuditProjection(final boolean distinct) {
public EntityAuditProjection(String alias, boolean distinct) {
this.alias = alias;
this.distinct = distinct;
}
@Override
public Triple<String, String, Boolean> getData(final EnversService enversService) {
public ProjectionData getData(final EnversService enversService) {
// no property is selected, instead the whole entity (alias) is selected
return Triple.make( null, null, distinct );
return new ProjectionData( null, alias, null, distinct );
}
@Override

View File

@ -8,7 +8,6 @@ package org.hibernate.envers.query.projection.internal;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.internal.entities.EntityInstantiator;
import org.hibernate.envers.internal.tools.Triple;
import org.hibernate.envers.query.internal.property.PropertyNameGetter;
import org.hibernate.envers.query.projection.AuditProjection;
@ -16,21 +15,23 @@ import org.hibernate.envers.query.projection.AuditProjection;
* @author Adam Warski (adam at warski dot org)
*/
public class PropertyAuditProjection implements AuditProjection {
private final String alias;
private final PropertyNameGetter propertyNameGetter;
private final String function;
private final boolean distinct;
public PropertyAuditProjection(PropertyNameGetter propertyNameGetter, String function, boolean distinct) {
public PropertyAuditProjection(String alias, PropertyNameGetter propertyNameGetter, String function, boolean distinct) {
this.alias = alias;
this.propertyNameGetter = propertyNameGetter;
this.function = function;
this.distinct = distinct;
}
@Override
public Triple<String, String, Boolean> getData(EnversService enversService) {
public ProjectionData getData(EnversService enversService) {
String propertyName = propertyNameGetter.get( enversService );
return Triple.make( function, propertyName, distinct );
return new ProjectionData( function, alias, propertyName, distinct );
}
@Override

View File

@ -25,7 +25,8 @@ import static org.junit.Assert.assertEquals;
/**
* @author Felix Feisst (feisst dot felix at gmail dot com)
*/
public class AssociationToOneQueryTest extends BaseEnversJPAFunctionalTestCase {
@SuppressWarnings("unchecked")
public class AssociationToOneInnerJoinQueryTest extends BaseEnversJPAFunctionalTestCase {
private Car vw;
private Car ford;
@ -50,7 +51,7 @@ public class AssociationToOneQueryTest extends BaseEnversJPAFunctionalTestCase {
em.getTransaction().begin();
address1 = new Address( "Freiburgerstrasse", 5 );
em.persist( address1 );
address2 = new Address( "Hindenburgstrasse", 20 );
address2 = new Address( "Hindenburgstrasse", 30 );
em.persist( address2 );
vwOwner = new Person( "VW owner", 20, address1 );
em.persist( vwOwner );
@ -86,7 +87,7 @@ public class AssociationToOneQueryTest extends BaseEnversJPAFunctionalTestCase {
assertEquals( "Unexpected single car at revision 1", ford.getId(), result1.getId() );
Car result2 = (Car) auditReader.createQuery().forEntitiesAtRevision( Car.class, 1 ).traverseRelation( "owner", JoinType.INNER ).traverseRelation( "address", JoinType.INNER )
.add( AuditEntity.property( "number" ).eq( 20 ) ).getSingleResult();
.add( AuditEntity.property( "number" ).eq( 30 ) ).getSingleResult();
assertEquals( "Unexpected single car at revision 1", toyota.getId(), result2.getId() );
List<Car> resultList1 = auditReader.createQuery().forEntitiesAtRevision( Car.class, 1 ).traverseRelation( "owner", JoinType.INNER )
@ -160,6 +161,36 @@ public class AssociationToOneQueryTest extends BaseEnversJPAFunctionalTestCase {
assertEquals( "Unexpected number at index 1", Integer.valueOf( 5 ), index1[1] );
final Object[] index2 = list4.get( 2 );
assertEquals( "Unexpected owner at index 2", toyotaOwner.getId(), ( (Person) index2[0] ).getId() );
assertEquals( "Unexpected number at index 2", Integer.valueOf( 20 ), index2[1] );
assertEquals( "Unexpected number at index 2", Integer.valueOf( 30 ), index2[1] );
}
@Test
public void testDisjunctionOfPropertiesFromDifferentEntities() {
AuditReader auditReader = getAuditReader();
// all cars where the owner has an age of 20 or lives in an address with number 30.
List<Car> resultList = auditReader.createQuery()
.forEntitiesAtRevision( Car.class, 1 )
.traverseRelation( "owner", JoinType.INNER, "p" )
.traverseRelation( "address", JoinType.INNER, "a" )
.up().up().add( AuditEntity.disjunction().add(AuditEntity.property( "p", "age" )
.eq( 20 ) ).add( AuditEntity.property( "a", "number" ).eq( 30 ) ) )
.addOrder( AuditEntity.property( "make" ).asc() ).getResultList();
assertEquals( "Expected two cars to be returned, Toyota and VW", 2, resultList.size() );
assertEquals( "Unexpected car at index 0", toyota.getId(), resultList.get(0).getId() );
assertEquals( "Unexpected car at index 1", vw.getId(), resultList.get(1).getId() );
}
@Test
public void testComparisonOfTwoPropertiesFromDifferentEntities() {
AuditReader auditReader = getAuditReader();
// the car where the owner age is equal to the owner address number.
Car result = (Car) auditReader.createQuery()
.forEntitiesAtRevision( Car.class, 1 )
.traverseRelation( "owner", JoinType.INNER, "p" )
.traverseRelation( "address", JoinType.INNER, "a" )
.up().up().add(AuditEntity.property( "p", "age" )
.eqProperty( "a", "number" ) ).getSingleResult();
assertEquals( "Unexpected car returned", toyota.getId(), result.getId() );
}
}

View File

@ -0,0 +1,136 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.envers.test.integration.query;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.criteria.JoinType;
import org.hibernate.envers.AuditReader;
import org.hibernate.envers.query.AuditEntity;
import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase;
import org.hibernate.envers.test.Priority;
import org.hibernate.envers.test.integration.query.entities.Address;
import org.hibernate.envers.test.integration.query.entities.Car;
import org.hibernate.envers.test.integration.query.entities.Person;
import org.junit.Test;
/**
* @author Felix Feisst (feisst dot felix at gmail dot com)
*/
@SuppressWarnings("unchecked")
public class AssociationToOneLeftJoinQueryTest extends BaseEnversJPAFunctionalTestCase {
private Car car1;
private Car car2;
private Car car3;
private Person person1;
private Person person2;
private Address address1;
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] { Car.class, Person.class, Address.class };
}
@Test
@Priority(10)
public void initData() {
final EntityManager em = getEntityManager();
// revision 1
em.getTransaction().begin();
address1 = new Address("address1", 1);
em.persist(address1);
Address address2 = new Address("address2", 2);
em.persist(address2);
person1 = new Person("person1", 30, address1);
em.persist(person1);
person2 = new Person("person2", 20, null);
em.persist(person2);
Person person3 = new Person("person3", 10, address1);
em.persist(person3);
car1 = new Car("car1");
car1.setOwner(person1);
em.persist(car1);
car2 = new Car("car2");
car2.setOwner(person2);
em.persist(car2);
car3 = new Car("car3");
em.persist(car3);
em.getTransaction().commit();
// revision 2
em.getTransaction().begin();
person2.setAge(21);
em.getTransaction().commit();
}
@Test
public void testLeftJoinOnAuditedEntity() {
final AuditReader auditReader = getAuditReader();
// all cars where the owner has an age of 20 or where there is no owner at all
List<Car> resultList = auditReader.createQuery()
.forEntitiesAtRevision( Car.class, 1 )
.traverseRelation( "owner", JoinType.LEFT, "p" )
.up().add( AuditEntity.or( AuditEntity.property( "p", "age").eq( 20 ),
AuditEntity.relatedId( "owner" ).eq( null ) ) )
.addOrder( AuditEntity.property( "make" ).asc() ).getResultList();
assertEquals( "The result list should have 2 results, car1 because its owner has an age of 30 and car3 because it has no owner at all", 2, resultList.size() );
Car car0 = resultList.get(0);
Car car1 = resultList.get(1);
assertEquals( "Unexpected car at index 0", car2.getId(), car0.getId() );
assertEquals( "Unexpected car at index 0", car3.getId(), car1.getId() );
}
/**
* In a first attempt to implement left joins in Envers, a full join
* has been performed and than the entities has been filtered in the
* where clause. However, this approach did only work for inner joins
* but not for left joins. One of the defects in this approach is,
* that audit entities, which have a null 'relatedId' are and do not
* match the query criterias, still joined to other entities which matched
* match the query criterias.
* This test ensures that this defect is no longer in the current implementation.
*/
@Test
public void testEntitiesWithANullRelatedIdAreNotJoinedToOtherEntities() {
final AuditReader auditReader = getAuditReader();
List<Car> resultList = auditReader.createQuery()
.forEntitiesAtRevision( Car.class, 1 )
.traverseRelation( "owner", JoinType.LEFT, "p" )
.up().add( AuditEntity.and( AuditEntity.property( "make" ).eq( "car3" ), AuditEntity.property( "p", "age" ).eq( 30 ) ) )
.getResultList();
assertTrue( "Expected no cars to be returned, because car3 does not have an owner", resultList.isEmpty() );
}
/**
* In a first attempt to implement left joins in Envers, a full join
* has been performed and than the entities has been filtered in the
* where clause. However, this approach did only work for inner joins
* but not for left joins. One of the defects in this approach is,
* that audit entities, which have a null 'relatedId' and do match
* the query criterias, have been returned multiple times by a query.
* This test ensures that this defect is no longer in the current implementation.
*/
@Test
public void testEntitiesWithANullRelatedIdAreNotReturnedMoreThanOnce() {
final AuditReader auditReader = getAuditReader();
List<Car> resultList = auditReader.createQuery()
.forEntitiesAtRevision( Car.class, 1 )
.traverseRelation( "owner", JoinType.LEFT, "p" )
.up().add( AuditEntity.or( AuditEntity.property( "make" ).eq( "car3" ), AuditEntity.property( "p", "age" ).eq( 10 ) ) )
.getResultList();
assertEquals( "Expected car3 to be returned but only once", 1, resultList.size() );
assertEquals( "Unexpected car at index 0", car3.getId(), resultList.get(0).getId() );
}
}