HHH-11452 Extended audit query API to specify the use of scalar

functions.
This commit is contained in:
Felix Feisst 2017-03-16 09:51:57 -04:00 committed by Chris Cranford
parent bb09222102
commit 640bd85975
15 changed files with 925 additions and 83 deletions

View File

@ -11,8 +11,10 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.hibernate.envers.configuration.Configuration;
import org.hibernate.envers.internal.tools.MutableBoolean; import org.hibernate.envers.internal.tools.MutableBoolean;
import org.hibernate.envers.internal.tools.MutableInteger; import org.hibernate.envers.internal.tools.MutableInteger;
import org.hibernate.envers.query.criteria.AuditFunction;
/** /**
* Parameters of a query, built using {@link QueryBuilder}. * Parameters of a query, built using {@link QueryBuilder}.
@ -311,6 +313,96 @@ public class Parameters {
} }
} }
/**
* Add where clause with a function call on the left and a scalar value on the right.
*
* @param configuration the configuration.
* @param function the function.
* @param op the operator.
* @param value the scalar value.
*/
public void addWhereWithFunction(Configuration configuration, AuditFunction function, String op, Object value) {
final StringBuilder expression = new StringBuilder();
QueryBuilder.appendFunctionArgument( configuration, queryParamCounter, localQueryParamValues, alias, expression, function );
expression.append( ' ' ).append( op );
String queryParam = generateQueryParam();
localQueryParamValues.put( queryParam, value );
expression.append( ' ' ).append( ':' ).append( queryParam );
expressions.add( expression.toString() );
}
/**
* Add a where clause with a function call on the left and an optionally aliased property on the right.
*
* @param configuration the configuration.
* @param function the function.
* @param op the operator.
* @param aliasRight the optional alias of the right property, may be {@literal null}
* @param right the property.
*/
public void addWhereWithFunction(Configuration configuration, AuditFunction function, String op, String aliasRight, String right) {
final StringBuilder expression = new StringBuilder();
QueryBuilder.appendFunctionArgument( configuration, queryParamCounter, localQueryParamValues, alias, expression, function );
expression.append( ' ' ).append( op ).append( ' ' );
if ( aliasRight != null ) {
expression.append( aliasRight ).append( '.' );
}
expression.append( right );
expressions.add( expression.toString() );
}
/**
* Adds a where clause with a left (optionally aliased) property and a function call on the right side.
*
* @param configuration the configuration.
* @param aliasLeft the optional alias of the left property, may be {@literal null}
* @param left the property.
* @param op the operator.
* @param function the function.
*/
public void addWhereWithFunction(Configuration configuration, String aliasLeft, String left, String op, AuditFunction function) {
final StringBuilder expression = new StringBuilder();
if ( aliasLeft != null ) {
expression.append( aliasLeft ).append( '.' );
}
expression.append( left );
expression.append( ' ' ).append( op ).append( ' ' );
QueryBuilder.appendFunctionArgument( configuration, queryParamCounter, localQueryParamValues, alias, expression, function );
expressions.add( expression.toString() );
}
/**
* Adds a where clause with a function call on both the left and right of the predicate.
*
* @param configuration the configuration.
* @param left the left-side function.
* @param op the operator.
* @param right the right-side function.
*/
public void addWhereWithFunction(Configuration configuration, AuditFunction left, String op, AuditFunction right) {
final StringBuilder expression = new StringBuilder();
QueryBuilder.appendFunctionArgument( configuration, queryParamCounter, localQueryParamValues, alias, expression, left );
expression.append( ' ' ).append( op ).append( ' ' );
QueryBuilder.appendFunctionArgument( configuration, queryParamCounter, localQueryParamValues, alias, expression, right );
expressions.add( expression.toString() );
}
private void append(StringBuilder sb, String toAppend, MutableBoolean isFirst) { private void append(StringBuilder sb, String toAppend, MutableBoolean isFirst) {
if ( !isFirst.isSet() ) { if ( !isFirst.isSet() ) {
sb.append( " " ).append( connective ).append( " " ); sb.append( " " ).append( connective ).append( " " );

View File

@ -17,11 +17,14 @@ import jakarta.persistence.criteria.JoinType;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.envers.RevisionType; import org.hibernate.envers.RevisionType;
import org.hibernate.envers.configuration.Configuration;
import org.hibernate.envers.function.OrderByFragmentFunction; import org.hibernate.envers.function.OrderByFragmentFunction;
import org.hibernate.envers.internal.entities.RevisionTypeType; import org.hibernate.envers.internal.entities.RevisionTypeType;
import org.hibernate.envers.internal.tools.MutableInteger; import org.hibernate.envers.internal.tools.MutableInteger;
import org.hibernate.envers.internal.tools.StringTools; import org.hibernate.envers.internal.tools.StringTools;
import org.hibernate.envers.internal.tools.Triple; import org.hibernate.envers.internal.tools.Triple;
import org.hibernate.envers.query.criteria.AuditFunction;
import org.hibernate.envers.query.criteria.AuditProperty;
import org.hibernate.envers.tools.Pair; import org.hibernate.envers.tools.Pair;
import org.hibernate.query.Query; import org.hibernate.query.Query;
import org.hibernate.query.internal.QueryLiteralHelper; import org.hibernate.query.internal.QueryLiteralHelper;
@ -62,6 +65,10 @@ public class QueryBuilder {
* A list of complete projection definitions: either a sole property name, or a function(property name). * A list of complete projection definitions: either a sole property name, or a function(property name).
*/ */
private final List<String> projections; private final List<String> projections;
/**
* Values of parameters used in projections.
*/
private final Map<String, Object> projectionQueryParamValues;
private final List<Pair<String, String>> orderFragments; private final List<Pair<String, String>> orderFragments;
@ -100,6 +107,7 @@ public class QueryBuilder {
froms = new ArrayList<>(); froms = new ArrayList<>();
orders = new ArrayList<>(); orders = new ArrayList<>();
projections = new ArrayList<>(); projections = new ArrayList<>();
projectionQueryParamValues = new HashMap<>();
orderFragments = new ArrayList<>(); orderFragments = new ArrayList<>();
addFrom( entityName, alias, true ); addFrom( entityName, alias, true );
@ -120,6 +128,7 @@ public class QueryBuilder {
froms = new ArrayList<>( other.froms ); froms = new ArrayList<>( other.froms );
orders = new ArrayList<>( other.orders ); orders = new ArrayList<>( other.orders );
projections = new ArrayList<>( other.projections ); projections = new ArrayList<>( other.projections );
projectionQueryParamValues = new HashMap<>( other.projectionQueryParamValues );
orderFragments = new ArrayList<>( other.orderFragments ); orderFragments = new ArrayList<>( other.orderFragments );
} }
@ -205,6 +214,48 @@ public class QueryBuilder {
} }
} }
public void addProjection(Configuration configuration, AuditFunction function) {
final StringBuilder expression = new StringBuilder();
appendFunctionArgument( configuration, paramCounter, projectionQueryParamValues, alias, expression, function );
projections.add( expression.toString() );
}
protected static void appendFunctionArgument(
Configuration configuration,
MutableInteger paramCounter,
Map<String, Object> queryParamValues,
String alias,
StringBuilder expression,
Object argument) {
if ( argument instanceof AuditFunction ) {
AuditFunction function = (AuditFunction) argument;
expression.append( function.getFunction() ).append( '(' );
boolean first = true;
for ( final Object innerArg : function.getArguments() ) {
if ( !first ) {
expression.append( ',' );
}
appendFunctionArgument( configuration, paramCounter, queryParamValues, alias, expression, innerArg );
first = false;
}
expression.append( ')' );
}
else if ( argument instanceof AuditProperty ) {
AuditProperty<?> property = (AuditProperty<?>) argument;
String propertyAlias = property.getAlias( alias );
if ( propertyAlias != null ) {
expression.append( propertyAlias ).append( '.' );
}
String propertyName = property.getPropertyNameGetter().get( configuration );
expression.append( propertyName );
}
else {
String queryParam = "_p" + paramCounter.getAndIncrease();
queryParamValues.put( queryParam, argument );
expression.append( ':' ).append( queryParam );
}
}
/** /**
* Builds the given query, appending results to the given string buffer, and adding all query parameter values * Builds the given query, appending results to the given string buffer, and adding all query parameter values
* that are used to the map provided. * that are used to the map provided.
@ -222,6 +273,7 @@ public class QueryBuilder {
// all aliases separated with commas // all aliases separated with commas
StringTools.append( sb, getSelectAliasList().iterator(), ", " ); StringTools.append( sb, getSelectAliasList().iterator(), ", " );
} }
queryParamValues.putAll( projectionQueryParamValues );
sb.append( " from " ); sb.append( " from " );
// all from entities with aliases // all from entities with aliases
boolean first = true; boolean first = true;

View File

@ -6,10 +6,15 @@
*/ */
package org.hibernate.envers.query; package org.hibernate.envers.query;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.hibernate.envers.RevisionType; import org.hibernate.envers.RevisionType;
import org.hibernate.envers.query.criteria.AuditConjunction; import org.hibernate.envers.query.criteria.AuditConjunction;
import org.hibernate.envers.query.criteria.AuditCriterion; import org.hibernate.envers.query.criteria.AuditCriterion;
import org.hibernate.envers.query.criteria.AuditDisjunction; import org.hibernate.envers.query.criteria.AuditDisjunction;
import org.hibernate.envers.query.criteria.AuditFunction;
import org.hibernate.envers.query.criteria.AuditId; import org.hibernate.envers.query.criteria.AuditId;
import org.hibernate.envers.query.criteria.AuditProperty; import org.hibernate.envers.query.criteria.AuditProperty;
import org.hibernate.envers.query.criteria.AuditRelatedId; import org.hibernate.envers.query.criteria.AuditRelatedId;
@ -176,4 +181,30 @@ public class AuditEntity {
public static AuditProjection selectEntity(boolean distinct) { public static AuditProjection selectEntity(boolean distinct) {
return new EntityAuditProjection( null, distinct ); return new EntityAuditProjection( null, distinct );
} }
/**
* Create restrictions or projections using a function.
* <p>
* Examples:
* <ul>
* <li>AuditEntity.function("upper", AuditEntity.property("prop"))</li>
* <li>AuditEntity.function("substring", AuditEntity.property("prop"), 3, 2)</li>
* <li>AuditEntity.function("concat", AuditEntity.function("upper", AuditEntity.property("prop1")),
* AuditEntity.function("substring", AuditEntity.property("prop2"), 1, 2))</li>
* </ul>
*
* @param function the name of the function
* @param arguments the arguments of the function. A function argument can either be
* <ul>
* <li>a primitive value like a Number or a String</li>
* <li>an AuditProperty (see {@link #property(String)})</li>
* <li>an other AuditFunction</li>
* </ul>
* @return
*/
public static AuditFunction function(final String function, final Object... arguments) {
List<Object> argumentList = new ArrayList<>();
Collections.addAll( argumentList, arguments );
return new AuditFunction( function, argumentList );
}
} }

View File

@ -0,0 +1,241 @@
/*
* 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;
import java.util.List;
import java.util.Map;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.internal.entities.EntityInstantiator;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.QueryBuilder;
import org.hibernate.envers.query.criteria.internal.FunctionFunctionAuditExpression;
import org.hibernate.envers.query.criteria.internal.PropertyFunctionAuditExpression;
import org.hibernate.envers.query.criteria.internal.SimpleFunctionAuditExpression;
import org.hibernate.envers.query.projection.AuditProjection;
/**
* Create restrictions or projections using a function.
*
* @author Felix Feisst (feisst dot felix at gmail dot com)
*/
public class AuditFunction implements AuditProjection {
private final String function;
private final List<Object> arguments;
public AuditFunction(String function, List<Object> arguments) {
this.function = function;
this.arguments = arguments;
}
public String getFunction() {
return function;
}
public List<Object> getArguments() {
return arguments;
}
/**
* Apply an "equal" constraint
*/
public AuditCriterion eq(Object value) {
return new SimpleFunctionAuditExpression( this, value, "=" );
}
/**
* Apply a "not equal" constraint
*/
public AuditCriterion ne(Object value) {
return new SimpleFunctionAuditExpression( this, value, "<>" );
}
/**
* Apply a "greater than" constraint
*/
public AuditCriterion gt(Object value) {
return new SimpleFunctionAuditExpression( this, value, ">" );
}
/**
* Apply a "less than" constraint
*/
public AuditCriterion lt(Object value) {
return new SimpleFunctionAuditExpression( this, value, "<" );
}
/**
* Apply a "less than or equal" constraint
*/
public AuditCriterion le(Object value) {
return new SimpleFunctionAuditExpression( this, value, "<=" );
}
/**
* Apply a "greater than or equal" constraint
*/
public AuditCriterion ge(Object value) {
return new SimpleFunctionAuditExpression( this, value, ">=" );
}
/**
* Apply an "equal" constraint to a property
*/
public AuditCriterion eqProperty(String propertyName) {
return eqProperty( null, propertyName );
}
/**
* Apply an "equal" constraint to a property
*
* @param alias the alias of the entity which owns the property.
*/
public AuditCriterion eqProperty(String alias, String propertyName) {
return new PropertyFunctionAuditExpression( this, alias, propertyName, "=" );
}
/**
* Apply a "not equal" constraint to a property
*/
public AuditCriterion neProperty(String propertyName) {
return neProperty( null, propertyName );
}
/**
* Apply a "not equal" constraint to a property
*
* @param alias the alias of the entity which owns the property.
*/
public AuditCriterion neProperty(String alias, String propertyName) {
return new PropertyFunctionAuditExpression( this, alias, propertyName, "<>" );
}
/**
* Apply a "less than" constraint to a property
*/
public AuditCriterion ltProperty(String propertyName) {
return ltProperty( null, propertyName );
}
/**
* Apply a "less than" constraint to a property
*
* @param alias the alias of the entity which owns the property.
*/
public AuditCriterion ltProperty(String alias, String propertyName) {
return new PropertyFunctionAuditExpression( this, alias, propertyName, "<" );
}
/**
* Apply a "less than or equal" constraint to a property
*/
public AuditCriterion leProperty(String propertyName) {
return leProperty( null, propertyName );
}
/**
* Apply a "less than or equal" constraint to a property
*
* @param alias the alias of the entity which owns the property.
*/
public AuditCriterion leProperty(String alias, String propertyName) {
return new PropertyFunctionAuditExpression( this, alias, propertyName, "<=" );
}
/**
* Apply a "greater than" constraint to a property
*/
public AuditCriterion gtProperty(String propertyName) {
return gtProperty( null, propertyName );
}
/**
* Apply a "greater than" constraint to a property
*
* @param alias the alias of the entity which owns the property.
*/
public AuditCriterion gtProperty(String alias, String propertyName) {
return new PropertyFunctionAuditExpression( this, alias, propertyName, ">" );
}
/**
* Apply a "greater than or equal" constraint to a property
*/
public AuditCriterion geProperty(String propertyName) {
return geProperty( null, propertyName );
}
/**
* Apply a "greater than or equal" constraint to a property
*
* @param alias the alias of the entity which owns the property.
*/
public AuditCriterion geProperty(String alias, String propertyName) {
return new PropertyFunctionAuditExpression( this, alias, propertyName, ">=" );
}
/**
* Apply an "equal" constraint to another function
*/
public AuditCriterion eqFunction(AuditFunction otherFunction) {
return new FunctionFunctionAuditExpression( this, otherFunction, "=" );
}
/**
* Apply a "not equal" constraint to another function
*/
public AuditCriterion neFunction(AuditFunction otherFunction) {
return new FunctionFunctionAuditExpression( this, otherFunction, "<>" );
}
/**
* Apply a "less than" constraint to another function
*/
public AuditCriterion ltFunction(AuditFunction otherFunction) {
return new FunctionFunctionAuditExpression( this, otherFunction, "<" );
}
/**
* Apply a "less than or equal" constraint to another function
*/
public AuditCriterion leFunction(AuditFunction otherFunction) {
return new FunctionFunctionAuditExpression( this, otherFunction, "<=" );
}
/**
* Apply a "greater than" constraint to another function
*/
public AuditCriterion gtFunction(AuditFunction otherFunction) {
return new FunctionFunctionAuditExpression( this, otherFunction, ">" );
}
/**
* Apply a "greater than or equal" constraint to another function
*/
public AuditCriterion geFunction(AuditFunction otherFunction) {
return new FunctionFunctionAuditExpression( this, otherFunction, ">=" );
}
@Override
public void addProjectionToQuery(EnversService enversService, AuditReaderImplementor auditReader,
Map<String, String> aliasToEntityNameMap, String baseAlias, QueryBuilder queryBuilder) {
queryBuilder.addProjection( enversService.getConfig(), this );
}
@Override
public Object convertQueryResult(EnversService enversService, EntityInstantiator entityInstantiator,
String entityName, Number revision, Object value) {
return value;
}
@Override
public String getAlias(String baseAlias) {
return baseAlias;
}
}

View File

@ -7,11 +7,15 @@
package org.hibernate.envers.query.criteria; package org.hibernate.envers.query.criteria;
import java.util.Collection; import java.util.Collection;
import java.util.Map;
import org.hibernate.envers.boot.internal.EnversService; import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.configuration.Configuration;
import org.hibernate.envers.internal.entities.EntityInstantiator; import org.hibernate.envers.internal.entities.EntityInstantiator;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.QueryBuilder;
import org.hibernate.envers.query.criteria.internal.BetweenAuditExpression; import org.hibernate.envers.query.criteria.internal.BetweenAuditExpression;
import org.hibernate.envers.query.criteria.internal.CriteriaTools;
import org.hibernate.envers.query.criteria.internal.FunctionPropertyAuditExpression;
import org.hibernate.envers.query.criteria.internal.IlikeAuditExpression; import org.hibernate.envers.query.criteria.internal.IlikeAuditExpression;
import org.hibernate.envers.query.criteria.internal.InAuditExpression; import org.hibernate.envers.query.criteria.internal.InAuditExpression;
import org.hibernate.envers.query.criteria.internal.NotNullAuditExpression; import org.hibernate.envers.query.criteria.internal.NotNullAuditExpression;
@ -42,6 +46,15 @@ public class AuditProperty<T> implements AuditProjection {
this.propertyNameGetter = propertyNameGetter; this.propertyNameGetter = propertyNameGetter;
} }
@Override
public String getAlias(String baseAlias) {
return alias == null ? baseAlias : alias;
}
public PropertyNameGetter getPropertyNameGetter() {
return propertyNameGetter;
}
public AuditCriterion hasChanged() { public AuditCriterion hasChanged() {
return new SimpleAuditExpression( alias, new ModifiedFlagPropertyName( propertyNameGetter ), true, "=" ); return new SimpleAuditExpression( alias, new ModifiedFlagPropertyName( propertyNameGetter ), true, "=" );
} }
@ -280,6 +293,48 @@ public class AuditProperty<T> implements AuditProjection {
return new PropertyAuditExpression( alias, propertyNameGetter, otherAlias, otherPropertyName, ">=" ); return new PropertyAuditExpression( alias, propertyNameGetter, otherAlias, otherPropertyName, ">=" );
} }
/**
* Apply an "equal" constraint to a function
*/
public AuditCriterion eqFunction(AuditFunction otherFunction) {
return new FunctionPropertyAuditExpression( alias, propertyNameGetter, otherFunction, "=" );
}
/**
* Apply a "not equal" constraint to a function
*/
public AuditCriterion neFunction(AuditFunction otherFunction) {
return new FunctionPropertyAuditExpression( alias, propertyNameGetter, otherFunction, "<>" );
}
/**
* Apply a "less than" constraint to a function
*/
public AuditCriterion ltFunction(AuditFunction otherFunction) {
return new FunctionPropertyAuditExpression( alias, propertyNameGetter, otherFunction, "<" );
}
/**
* Apply a "less than or equal" constraint to a function
*/
public AuditCriterion leFunction(AuditFunction otherFunction) {
return new FunctionPropertyAuditExpression( alias, propertyNameGetter, otherFunction, "<=" );
}
/**
* Apply a "greater than" constraint to a function
*/
public AuditCriterion gtFunction(AuditFunction otherFunction) {
return new FunctionPropertyAuditExpression( alias, propertyNameGetter, otherFunction, ">" );
}
/**
* Apply a "greater than or equal" constraint to a function
*/
public AuditCriterion geFunction(AuditFunction otherFunction) {
return new FunctionPropertyAuditExpression( alias, propertyNameGetter, otherFunction, ">=" );
}
/** /**
* Apply an "is not null" constraint to the another property * Apply an "is not null" constraint to the another property
*/ */
@ -349,16 +404,21 @@ public class AuditProperty<T> implements AuditProjection {
// Projection on this property // Projection on this property
/** @Override
* @deprecated since 6.0, use {@link #getData(Configuration)} instead. public void addProjectionToQuery(EnversService enversService, AuditReaderImplementor auditReader,
*/ Map<String, String> aliasToEntityNameMap, String baseAlias, QueryBuilder queryBuilder) {
@Deprecated String projectionEntityAlias = getAlias( baseAlias );
public ProjectionData getData(EnversService enversService) { String projectionEntityName = aliasToEntityNameMap.get( projectionEntityAlias );
return new ProjectionData( null, alias, propertyNameGetter.get( enversService.getConfig() ), false ); String propertyName = CriteriaTools.determinePropertyName(
} enversService,
auditReader,
public ProjectionData getData(Configuration configuration) { projectionEntityName,
return new ProjectionData( null, alias, propertyNameGetter.get( configuration ), false ); propertyNameGetter );
queryBuilder.addProjection(
null,
projectionEntityAlias,
propertyName,
false );
} }
// Order // Order

View File

@ -0,0 +1,48 @@
/*
* 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;
import org.hibernate.envers.query.criteria.AuditFunction;
/**
* An audit query criterion that defines a predicate where both sides are a function.
*
* @author Felix Feisst (feisst dot felix at gmail dot com)
*/
public class FunctionFunctionAuditExpression implements AuditCriterion {
private AuditFunction leftFunction;
private AuditFunction rightFunction;
private String op;
public FunctionFunctionAuditExpression(
AuditFunction leftFunction,
AuditFunction rightFunction,
String op) {
this.leftFunction = leftFunction;
this.rightFunction = rightFunction;
this.op = op;
}
@Override
public void addToQuery(
EnversService enversService,
AuditReaderImplementor auditReader,
Map<String, String> aliasToEntityNameMap,
String baseAlias,
QueryBuilder queryBuilder,
Parameters parameters) {
parameters.addWhereWithFunction( enversService.getConfig(), leftFunction, op, rightFunction );
}
}

View File

@ -0,0 +1,61 @@
/*
* 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;
import org.hibernate.envers.query.criteria.AuditFunction;
import org.hibernate.envers.query.internal.property.PropertyNameGetter;
/**
* An audit query criterion that defines a predicate that is a comparison between a function
* and an audit property expression.
*
* @author Felix Feisst (feisst dot felix at gmail dot com)
*/
public class FunctionPropertyAuditExpression implements AuditCriterion {
private String alias;
private PropertyNameGetter propertyNameGetter;
private AuditFunction function;
private String op;
public FunctionPropertyAuditExpression(
String alias,
PropertyNameGetter propertyNameGetter,
AuditFunction function,
String op) {
this.alias = alias;
this.propertyNameGetter = propertyNameGetter;
this.function = function;
this.op = op;
}
@Override
public void addToQuery(
EnversService enversService,
AuditReaderImplementor auditReader,
Map<String, String> aliasToEntityNameMap,
String baseAlias,
QueryBuilder queryBuilder,
Parameters parameters) {
String effectiveAlias = alias == null ? baseAlias : alias;
String entityName = aliasToEntityNameMap.get( effectiveAlias );
String propertyName = CriteriaTools.determinePropertyName(
enversService,
auditReader,
entityName,
propertyNameGetter );
CriteriaTools.checkPropertyNotARelation( enversService, entityName, propertyName );
parameters.addWhereWithFunction( enversService.getConfig(), effectiveAlias, propertyName, op, function );
}
}

View File

@ -0,0 +1,60 @@
/*
* 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;
import org.hibernate.envers.query.criteria.AuditFunction;
/**
* An audit query criterion where a function is compared to a property.
*
* @author Felix Feisst (feisst dot felix at gmail dot com)
*/
public class PropertyFunctionAuditExpression implements AuditCriterion {
private AuditFunction function;
private String otherAlias;
private String otherPropertyName;
private String op;
public PropertyFunctionAuditExpression(
AuditFunction function,
String otherAlias,
String otherPropertyName,
String op) {
this.function = function;
this.otherAlias = otherAlias;
this.otherPropertyName = otherPropertyName;
this.op = op;
}
@Override
public void addToQuery(
EnversService enversService,
AuditReaderImplementor auditReader,
Map<String, String> aliasToEntityNameMap,
String baseAlias,
QueryBuilder queryBuilder,
Parameters parameters) {
String effectiveOtherAlias = otherAlias == null ? baseAlias : otherAlias;
String otherEntityName = aliasToEntityNameMap.get( effectiveOtherAlias );
/*
* 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.addWhereWithFunction( enversService.getConfig(), function, op, effectiveOtherAlias, otherPropertyName );
}
}

View File

@ -0,0 +1,41 @@
/*
* 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;
import org.hibernate.envers.query.criteria.AuditFunction;
/**
* An audit query criterion that compares a function call with a scalar value.
*
* @author Felix Feisst (feisst dot felix at gmail dot com)
*/
public class SimpleFunctionAuditExpression implements AuditCriterion {
private AuditFunction function;
private Object value;
private String op;
public SimpleFunctionAuditExpression(AuditFunction function, Object value, String op) {
this.function = function;
this.value = value;
this.op = op;
}
@Override
public void addToQuery(EnversService enversService, AuditReaderImplementor versionsReader,
Map<String, String> aliasToEntityNameMap, String baseAlias, QueryBuilder qb, Parameters parameters) {
parameters.addWhereWithFunction( enversService.getConfig(), function, op, value );
}
}

View File

@ -135,22 +135,10 @@ public abstract class AbstractAuditQuery implements AuditQueryImplementor {
// Projection and order // Projection and order
public AuditQuery addProjection(AuditProjection projection) { public AuditQuery addProjection(AuditProjection projection) {
AuditProjection.ProjectionData projectionData = projection.getData( enversService.getConfig() ); String projectionEntityAlias = projection.getAlias( REFERENCED_ENTITY_ALIAS );
String projectionEntityAlias = projectionData.getAlias( REFERENCED_ENTITY_ALIAS );
String projectionEntityName = aliasToEntityNameMap.get( projectionEntityAlias ); String projectionEntityName = aliasToEntityNameMap.get( projectionEntityAlias );
registerProjection( projectionEntityName, projection ); registerProjection( projectionEntityName, projection );
String propertyName = CriteriaTools.determinePropertyName( projection.addProjectionToQuery( enversService, versionsReader, aliasToEntityNameMap, REFERENCED_ENTITY_ALIAS, qb );
enversService,
versionsReader,
projectionEntityName,
projectionData.getPropertyName()
);
qb.addProjection(
projectionData.getFunction(),
projectionEntityAlias,
propertyName,
projectionData.isDistinct()
);
return this; return this;
} }

View File

@ -159,22 +159,10 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
@Override @Override
public AuditAssociationQueryImpl<Q> addProjection(AuditProjection projection) { public AuditAssociationQueryImpl<Q> addProjection(AuditProjection projection) {
AuditProjection.ProjectionData projectionData = projection.getData( enversService.getConfig() ); String projectionEntityAlias = projection.getAlias( alias );
String projectionEntityAlias = projectionData.getAlias( alias );
String projectionEntityName = aliasToEntityNameMap.get( projectionEntityAlias ); String projectionEntityName = aliasToEntityNameMap.get( projectionEntityAlias );
String propertyName = CriteriaTools.determinePropertyName(
enversService,
auditReader,
projectionEntityName,
projectionData.getPropertyName()
);
queryBuilder.addProjection(
projectionData.getFunction(),
projectionEntityAlias,
propertyName,
projectionData.isDistinct()
);
registerProjection( projectionEntityName, projection ); registerProjection( projectionEntityName, projection );
projection.addProjectionToQuery( enversService, auditReader, aliasToEntityNameMap, alias, queryBuilder );
return this; return this;
} }

View File

@ -6,9 +6,12 @@
*/ */
package org.hibernate.envers.query.projection; package org.hibernate.envers.query.projection;
import java.util.Map;
import org.hibernate.envers.boot.internal.EnversService; import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.configuration.Configuration;
import org.hibernate.envers.internal.entities.EntityInstantiator; import org.hibernate.envers.internal.entities.EntityInstantiator;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.QueryBuilder;
/** /**
* @author Adam Warski (adam at warski dot org) * @author Adam Warski (adam at warski dot org)
@ -17,10 +20,28 @@ import org.hibernate.envers.internal.entities.EntityInstantiator;
public interface AuditProjection { public interface AuditProjection {
/** /**
* @param configuration the configuration * Adds an audit projection to the specified query.
* @return get the project data *
* @param enversService the Envers service
* @param auditReader the audit reader implementor
* @param aliasToEntityNameMap the entity name alias map
* @param baseAlias the base alias, if one is specified; may be {@literal null}
* @param queryBuilder the query builder
*/ */
ProjectionData getData(Configuration configuration); void addProjectionToQuery(
EnversService enversService,
AuditReaderImplementor auditReader,
Map<String, String> aliasToEntityNameMap,
String baseAlias,
QueryBuilder queryBuilder);
/**
* Get the alias associated with the audit projection.
*
* @param baseAlias the base alias if one exists; may be {@literal null}
* @return the alias
*/
String getAlias(String baseAlias);
/** /**
* @param enversService the Envers service * @param enversService the Envers service
@ -38,35 +59,4 @@ public interface AuditProjection {
final Object value 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

@ -9,8 +9,9 @@ package org.hibernate.envers.query.projection.internal;
import java.util.Map; import java.util.Map;
import org.hibernate.envers.boot.internal.EnversService; import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.configuration.Configuration;
import org.hibernate.envers.internal.entities.EntityInstantiator; import org.hibernate.envers.internal.entities.EntityInstantiator;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.QueryBuilder;
import org.hibernate.envers.query.projection.AuditProjection; import org.hibernate.envers.query.projection.AuditProjection;
/** /**
@ -28,9 +29,19 @@ public class EntityAuditProjection implements AuditProjection {
} }
@Override @Override
public ProjectionData getData(Configuration configuration) { public String getAlias(String baseAlias) {
// no property is selected, instead the whole entity (alias) is selected return alias == null ? baseAlias : alias;
return new ProjectionData( null, alias, null, distinct ); }
@Override
public void addProjectionToQuery(EnversService enversService, AuditReaderImplementor auditReader,
Map<String, String> aliasToEntityNameMap, String baseAlias, QueryBuilder queryBuilder) {
String projectionEntityAlias = getAlias( baseAlias );
queryBuilder.addProjection(
null,
projectionEntityAlias,
null,
distinct );
} }
@Override @Override

View File

@ -6,9 +6,13 @@
*/ */
package org.hibernate.envers.query.projection.internal; package org.hibernate.envers.query.projection.internal;
import java.util.Map;
import org.hibernate.envers.boot.internal.EnversService; import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.configuration.Configuration;
import org.hibernate.envers.internal.entities.EntityInstantiator; import org.hibernate.envers.internal.entities.EntityInstantiator;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.QueryBuilder;
import org.hibernate.envers.query.criteria.internal.CriteriaTools;
import org.hibernate.envers.query.internal.property.PropertyNameGetter; import org.hibernate.envers.query.internal.property.PropertyNameGetter;
import org.hibernate.envers.query.projection.AuditProjection; import org.hibernate.envers.query.projection.AuditProjection;
@ -30,10 +34,29 @@ public class PropertyAuditProjection implements AuditProjection {
} }
@Override @Override
public ProjectionData getData(Configuration configuration) { public String getAlias(String baseAlias) {
String propertyName = propertyNameGetter.get( configuration ); return alias == null ? baseAlias : alias;
}
return new ProjectionData( function, alias, propertyName, distinct ); @Override
public void addProjectionToQuery(
EnversService enversService,
AuditReaderImplementor auditReader,
Map<String, String> aliasToEntityNameMap,
String baseAlias,
QueryBuilder queryBuilder) {
String projectionEntityAlias = getAlias( baseAlias );
String projectionEntityName = aliasToEntityNameMap.get( projectionEntityAlias );
String propertyName = CriteriaTools.determinePropertyName(
enversService,
auditReader,
projectionEntityName,
propertyNameGetter );
queryBuilder.addProjection(
function,
projectionEntityAlias,
propertyName,
distinct );
} }
@Override @Override

View File

@ -0,0 +1,156 @@
/*
* 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.assertNotNull;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Id;
import org.hibernate.envers.Audited;
import org.hibernate.envers.query.AuditEntity;
import org.hibernate.orm.test.envers.BaseEnversJPAFunctionalTestCase;
import org.hibernate.orm.test.envers.Priority;
import org.junit.Test;
/**
* @author Felix Feisst (feisst dot felix at gmail dot com)
*/
public class AuditFunctionQueryTest extends BaseEnversJPAFunctionalTestCase {
@Entity
@Audited
public static class TestEntity {
@Id
private Long id;
private String string1;
private String string2;
private Integer int1;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getString1() {
return string1;
}
public void setString1(String string1) {
this.string1 = string1;
}
public String getString2() {
return string2;
}
public Integer getInt1() {
return int1;
}
public void setInt1(Integer int1) {
this.int1 = int1;
}
public void setString2(String string2) {
this.string2 = string2;
}
}
private TestEntity testEntity1;
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[]{ TestEntity.class };
}
@Test
@Priority(10)
public void initData() {
EntityManager em = getEntityManager();
em.getTransaction().begin();
testEntity1 = new TestEntity();
testEntity1.setId( 1L );
testEntity1.setString1( "abcdef" );
testEntity1.setString2( "42 - the truth" );
testEntity1.setInt1( 42 );
em.persist( testEntity1 );
em.getTransaction().commit();
}
@Test
public void testProjectionWithPropertyArgument() {
Object actual = getAuditReader().createQuery().forEntitiesAtRevision( TestEntity.class, 1 )
.addProjection( AuditEntity.function( "upper", AuditEntity.property( "string1" ) ) ).getSingleResult();
String expected = testEntity1.getString1().toUpperCase();
assertEquals( "Expected the property string1 to be upper case", expected, actual );
}
@Test
public void testProjectionWithPropertyAndSimpleArguments() {
Object actual = getAuditReader().createQuery().forEntitiesAtRevision( TestEntity.class, 1 )
.addProjection( AuditEntity.function( "substring", AuditEntity.property( "string1" ), 3, 2 ) ).getSingleResult();
// the sql substring indices are 1 based while java substring indices are 0 based
// the sql substring second parameter denotes the length of the substring
// while in java the scond argument denotes the end index
String expected = testEntity1.getString1().substring( 2, 4 );
assertEquals( "Expected the substring of the property string1", expected, actual );
}
@Test
public void testProjectionWithNestedFunction() {
Object actual = getAuditReader().createQuery().forEntitiesAtRevision( TestEntity.class, 1 )
.addProjection( AuditEntity.function( "concat",
AuditEntity.function( "upper", AuditEntity.property( "string1" ) ),
AuditEntity.function( "substring", AuditEntity.property( "string2" ), 1, 2 ) ) )
.getSingleResult();
final String expected = testEntity1.getString1().toUpperCase().concat( testEntity1.getString2().substring( 0, 2 ) );
assertEquals( "Expected the upper cased string1 to be concat with the first two characters of string2", expected, actual );
}
@Test
public void testComparisonFunctionWithValue() {
TestEntity entity = (TestEntity) getAuditReader().createQuery().forEntitiesAtRevision( TestEntity.class, 1 )
.add( AuditEntity.function( "substring", AuditEntity.property( "string1" ), 3, 2 ).eq( "cd" ) ).getSingleResult();
assertNotNull( "Expected the entity to be returned", entity );
assertEquals( "Expected the entity to be returned", testEntity1.getId(), entity.getId() );
}
@Test
public void testComparionFunctionWithProperty() {
TestEntity entity = (TestEntity) getAuditReader().createQuery().forEntitiesAtRevision( TestEntity.class, 1 )
.add( AuditEntity.function( "length", AuditEntity.property( "string2" ) ).ltProperty( "int1" ) ).getSingleResult();
assertNotNull( "Expected the entity to be returned", entity );
assertEquals( "Expected the entity to be returned", testEntity1.getId(), entity.getId() );
}
@Test
public void testComparisonFunctionWithFunction() {
TestEntity entity = (TestEntity) getAuditReader().createQuery().forEntitiesAtRevision( TestEntity.class, 1 )
.add( AuditEntity.function( "substring", AuditEntity.property( "string2" ), 1, 2 )
.eqFunction( AuditEntity.function( "str", AuditEntity.property( "int1" ) ) ) )
.getSingleResult();
assertNotNull( "Expected the entity to be returned", entity );
assertEquals( "Expected the entity to be returned", testEntity1.getId(), entity.getId() );
}
@Test
public void testComparisonPropertyWithFunction() {
TestEntity entity = (TestEntity) getAuditReader().createQuery().forEntitiesAtRevision( TestEntity.class, 1 )
.add( AuditEntity.property( "int1" ).gtFunction( AuditEntity.function( "length", AuditEntity.property( "string2" ) ) ) ).getSingleResult();
assertNotNull( "Expected the entity to be returned", entity );
assertEquals( "Expected the entity to be returned", testEntity1.getId(), entity.getId() );
}
}