HHH-11452 Added possibility to apply the like operator on a function criterion. Added possibility to use the id of an entity as function argument.

This commit is contained in:
Felix Feisst 2017-05-09 15:11:50 -04:00 committed by Chris Cranford
parent 640bd85975
commit e50037ec8c
8 changed files with 183 additions and 21 deletions

View File

@ -317,14 +317,28 @@ public class Parameters {
* Add where clause with a function call on the left and a scalar value on the right. * Add where clause with a function call on the left and a scalar value on the right.
* *
* @param configuration the configuration. * @param configuration the configuration.
* @param aliasToEntityNameMap alias to entity name map, never {@literal null}
* @param function the function. * @param function the function.
* @param op the operator. * @param op the operator.
* @param value the scalar value. * @param value the scalar value.
*/ */
public void addWhereWithFunction(Configuration configuration, AuditFunction function, String op, Object value) { public void addWhereWithFunction(
Configuration configuration,
Map<String, String> aliasToEntityNameMap,
AuditFunction function,
String op,
Object value) {
final StringBuilder expression = new StringBuilder(); final StringBuilder expression = new StringBuilder();
QueryBuilder.appendFunctionArgument( configuration, queryParamCounter, localQueryParamValues, alias, expression, function ); QueryBuilder.appendFunctionArgument(
configuration,
aliasToEntityNameMap,
queryParamCounter,
localQueryParamValues,
alias,
expression,
function
);
expression.append( ' ' ).append( op ); expression.append( ' ' ).append( op );
@ -339,15 +353,30 @@ public class Parameters {
* Add a where clause with a function call on the left and an optionally aliased property on the right. * Add a where clause with a function call on the left and an optionally aliased property on the right.
* *
* @param configuration the configuration. * @param configuration the configuration.
* @param aliasToEntityNameMap alias to entity name map, never {@literal null}
* @param function the function. * @param function the function.
* @param op the operator. * @param op the operator.
* @param aliasRight the optional alias of the right property, may be {@literal null} * @param aliasRight the optional alias of the right property, may be {@literal null}
* @param right the property. * @param right the property.
*/ */
public void addWhereWithFunction(Configuration configuration, AuditFunction function, String op, String aliasRight, String right) { public void addWhereWithFunction(
Configuration configuration,
Map<String, String> aliasToEntityNameMap,
AuditFunction function,
String op,
String aliasRight,
String right) {
final StringBuilder expression = new StringBuilder(); final StringBuilder expression = new StringBuilder();
QueryBuilder.appendFunctionArgument( configuration, queryParamCounter, localQueryParamValues, alias, expression, function ); QueryBuilder.appendFunctionArgument(
configuration,
aliasToEntityNameMap,
queryParamCounter,
localQueryParamValues,
alias,
expression,
function
);
expression.append( ' ' ).append( op ).append( ' ' ); expression.append( ' ' ).append( op ).append( ' ' );
@ -363,12 +392,19 @@ public class Parameters {
* Adds a where clause with a left (optionally aliased) property and a function call on the right side. * Adds a where clause with a left (optionally aliased) property and a function call on the right side.
* *
* @param configuration the configuration. * @param configuration the configuration.
* @param aliasToEntityNameMap alias to entity name map, never {@literal null}
* @param aliasLeft the optional alias of the left property, may be {@literal null} * @param aliasLeft the optional alias of the left property, may be {@literal null}
* @param left the property. * @param left the property.
* @param op the operator. * @param op the operator.
* @param function the function. * @param function the function.
*/ */
public void addWhereWithFunction(Configuration configuration, String aliasLeft, String left, String op, AuditFunction function) { public void addWhereWithFunction(
Configuration configuration,
Map<String, String> aliasToEntityNameMap,
String aliasLeft,
String left,
String op,
AuditFunction function) {
final StringBuilder expression = new StringBuilder(); final StringBuilder expression = new StringBuilder();
if ( aliasLeft != null ) { if ( aliasLeft != null ) {
@ -378,7 +414,15 @@ public class Parameters {
expression.append( ' ' ).append( op ).append( ' ' ); expression.append( ' ' ).append( op ).append( ' ' );
QueryBuilder.appendFunctionArgument( configuration, queryParamCounter, localQueryParamValues, alias, expression, function ); QueryBuilder.appendFunctionArgument(
configuration,
aliasToEntityNameMap,
queryParamCounter,
localQueryParamValues,
alias,
expression,
function
);
expressions.add( expression.toString() ); expressions.add( expression.toString() );
} }
@ -387,18 +431,40 @@ public class Parameters {
* Adds a where clause with a function call on both the left and right of the predicate. * Adds a where clause with a function call on both the left and right of the predicate.
* *
* @param configuration the configuration. * @param configuration the configuration.
* @param aliasToEntityNameMap alias to entity name map, never {@literal null}
* @param left the left-side function. * @param left the left-side function.
* @param op the operator. * @param op the operator.
* @param right the right-side function. * @param right the right-side function.
*/ */
public void addWhereWithFunction(Configuration configuration, AuditFunction left, String op, AuditFunction right) { public void addWhereWithFunction(
Configuration configuration,
Map<String, String> aliasToEntityNameMap,
AuditFunction left,
String op,
AuditFunction right) {
final StringBuilder expression = new StringBuilder(); final StringBuilder expression = new StringBuilder();
QueryBuilder.appendFunctionArgument( configuration, queryParamCounter, localQueryParamValues, alias, expression, left ); QueryBuilder.appendFunctionArgument(
configuration,
aliasToEntityNameMap,
queryParamCounter,
localQueryParamValues,
alias,
expression,
left
);
expression.append( ' ' ).append( op ).append( ' ' ); expression.append( ' ' ).append( op ).append( ' ' );
QueryBuilder.appendFunctionArgument( configuration, queryParamCounter, localQueryParamValues, alias, expression, right ); QueryBuilder.appendFunctionArgument(
configuration,
aliasToEntityNameMap,
queryParamCounter,
localQueryParamValues,
alias,
expression,
right
);
expressions.add( expression.toString() ); expressions.add( expression.toString() );
} }

View File

@ -12,18 +12,23 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import jakarta.persistence.criteria.JoinType; import jakarta.persistence.criteria.JoinType;
import org.hibernate.HibernateException;
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.boot.internal.EnversService;
import org.hibernate.envers.configuration.Configuration; 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.entities.mapper.id.QueryParameterData;
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.AuditFunction;
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.tools.Pair; import org.hibernate.envers.tools.Pair;
import org.hibernate.query.Query; import org.hibernate.query.Query;
@ -214,14 +219,23 @@ public class QueryBuilder {
} }
} }
public void addProjection(Configuration configuration, AuditFunction function) { public void addProjection(Configuration configuration, Map<String, String> aliasToEntityNameMap, AuditFunction function) {
final StringBuilder expression = new StringBuilder(); final StringBuilder expression = new StringBuilder();
appendFunctionArgument( configuration, paramCounter, projectionQueryParamValues, alias, expression, function ); appendFunctionArgument(
configuration,
aliasToEntityNameMap,
paramCounter,
projectionQueryParamValues,
alias,
expression,
function
);
projections.add( expression.toString() ); projections.add( expression.toString() );
} }
protected static void appendFunctionArgument( protected static void appendFunctionArgument(
Configuration configuration, Configuration configuration,
Map<String, String> aliasToEntityNameMap,
MutableInteger paramCounter, MutableInteger paramCounter,
Map<String, Object> queryParamValues, Map<String, Object> queryParamValues,
String alias, String alias,
@ -235,11 +249,42 @@ public class QueryBuilder {
if ( !first ) { if ( !first ) {
expression.append( ',' ); expression.append( ',' );
} }
appendFunctionArgument( configuration, paramCounter, queryParamValues, alias, expression, innerArg ); appendFunctionArgument(
configuration,
aliasToEntityNameMap,
paramCounter,
queryParamValues,
alias,
expression,
innerArg
);
first = false; first = false;
} }
expression.append( ')' ); expression.append( ')' );
} }
else if ( argument instanceof AuditId ) {
AuditId<?> id = (AuditId<?>) argument;
String prefix = configuration.getOriginalIdPropertyName();
String idAlias = id.getAlias( alias );
String entityName = aliasToEntityNameMap.get( idAlias );
/*
* Resolve the name of the id property by reusing the IdMapper.mapToQueryParametersFromId() method. Null is
* passed as value because only the name of the property is of interest. TODO: is there a better way to
* obtain the name of the id property?
*/
EnversService enversService = configuration.getEnversService();
List<QueryParameterData> parameters = enversService.getEntitiesConfigurations().get( entityName )
.getIdMapper()
.mapToQueryParametersFromId( null );
if ( parameters.size() != 1 ) {
throw new HibernateException( "Cannot add id property as function argument when id property is not a single column property" );
}
String propertyName = parameters.get( 0 ).getProperty( prefix );
if ( idAlias != null ) {
expression.append( idAlias ).append( '.' );
}
expression.append( propertyName );
}
else if ( argument instanceof AuditProperty ) { else if ( argument instanceof AuditProperty ) {
AuditProperty<?> property = (AuditProperty<?>) argument; AuditProperty<?> property = (AuditProperty<?>) argument;
String propertyAlias = property.getAlias( alias ); String propertyAlias = property.getAlias( alias );

View File

@ -55,6 +55,20 @@ public class AuditFunction implements AuditProjection {
return new SimpleFunctionAuditExpression( this, value, "<>" ); return new SimpleFunctionAuditExpression( this, value, "<>" );
} }
/**
* Apply a "like" constraint
*/
public AuditCriterion like(Object value) {
return new SimpleFunctionAuditExpression( this, value, " like " );
}
/**
* Apply a "like" constraint
*/
public AuditCriterion like(String value, MatchMode matchMode) {
return new SimpleFunctionAuditExpression( this, matchMode.toMatchString( value ), " like " );
}
/** /**
* Apply a "greater than" constraint * Apply a "greater than" constraint
*/ */
@ -222,9 +236,13 @@ public class AuditFunction implements AuditProjection {
} }
@Override @Override
public void addProjectionToQuery(EnversService enversService, AuditReaderImplementor auditReader, public void addProjectionToQuery(
Map<String, String> aliasToEntityNameMap, String baseAlias, QueryBuilder queryBuilder) { EnversService enversService,
queryBuilder.addProjection( enversService.getConfig(), this ); AuditReaderImplementor auditReader,
Map<String, String> aliasToEntityNameMap,
String baseAlias,
QueryBuilder queryBuilder) {
queryBuilder.addProjection( enversService.getConfig(), aliasToEntityNameMap, this );
} }
@Override @Override

View File

@ -43,6 +43,12 @@ public class FunctionFunctionAuditExpression implements AuditCriterion {
String baseAlias, String baseAlias,
QueryBuilder queryBuilder, QueryBuilder queryBuilder,
Parameters parameters) { Parameters parameters) {
parameters.addWhereWithFunction( enversService.getConfig(), leftFunction, op, rightFunction ); parameters.addWhereWithFunction(
enversService.getConfig(),
aliasToEntityNameMap,
leftFunction,
op,
rightFunction
);
} }
} }

View File

@ -56,6 +56,13 @@ public class FunctionPropertyAuditExpression implements AuditCriterion {
entityName, entityName,
propertyNameGetter ); propertyNameGetter );
CriteriaTools.checkPropertyNotARelation( enversService, entityName, propertyName ); CriteriaTools.checkPropertyNotARelation( enversService, entityName, propertyName );
parameters.addWhereWithFunction( enversService.getConfig(), effectiveAlias, propertyName, op, function ); parameters.addWhereWithFunction(
enversService.getConfig(),
aliasToEntityNameMap,
effectiveAlias,
propertyName,
op,
function
);
} }
} }

View File

@ -55,6 +55,13 @@ public class PropertyFunctionAuditExpression implements AuditCriterion {
if ( enversService.getEntitiesConfigurations().isVersioned( otherEntityName ) ) { if ( enversService.getEntitiesConfigurations().isVersioned( otherEntityName ) ) {
CriteriaTools.checkPropertyNotARelation( enversService, otherEntityName, otherPropertyName ); CriteriaTools.checkPropertyNotARelation( enversService, otherEntityName, otherPropertyName );
} }
parameters.addWhereWithFunction( enversService.getConfig(), function, op, effectiveOtherAlias, otherPropertyName ); parameters.addWhereWithFunction(
enversService.getConfig(),
aliasToEntityNameMap,
function,
op,
effectiveOtherAlias,
otherPropertyName
);
} }
} }

View File

@ -33,9 +33,14 @@ public class SimpleFunctionAuditExpression implements AuditCriterion {
} }
@Override @Override
public void addToQuery(EnversService enversService, AuditReaderImplementor versionsReader, public void addToQuery(
Map<String, String> aliasToEntityNameMap, String baseAlias, QueryBuilder qb, Parameters parameters) { EnversService enversService,
parameters.addWhereWithFunction( enversService.getConfig(), function, op, value ); AuditReaderImplementor versionsReader,
Map<String, String> aliasToEntityNameMap,
String baseAlias,
QueryBuilder qb,
Parameters parameters) {
parameters.addWhereWithFunction( enversService.getConfig(), aliasToEntityNameMap, function, op, value );
} }
} }

View File

@ -153,4 +153,12 @@ public class AuditFunctionQueryTest extends BaseEnversJPAFunctionalTestCase {
assertEquals( "Expected the entity to be returned", testEntity1.getId(), entity.getId() ); assertEquals( "Expected the entity to be returned", testEntity1.getId(), entity.getId() );
} }
@Test
public void testFunctionOnIdProperty() {
TestEntity entity = (TestEntity) getAuditReader().createQuery().forEntitiesAtRevision( TestEntity.class, 1 )
.add( AuditEntity.function( "str", AuditEntity.id() ).like( "%1%" ) ).getSingleResult();
assertNotNull( "Expected the entity to be returned", entity );
assertEquals( "Expected the entity to be returned", testEntity1.getId(), entity.getId() );
}
} }