HHH-16739 Fix several failures when comparing enum parameters with constant values
This commit is contained in:
parent
f24660e1fd
commit
e8acf51608
|
@ -158,7 +158,8 @@ public class InverseDistributionFunction extends AbstractSqmSelfRenderingFunctio
|
|||
return (ReturnableType<?>)
|
||||
getWithinGroup().getSortSpecifications().get( 0 )
|
||||
.getSortExpression()
|
||||
.getExpressible();
|
||||
.getExpressible()
|
||||
.getSqmType();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -26,4 +26,8 @@ import org.hibernate.type.descriptor.java.JavaType;
|
|||
* @author Steve Ebersole
|
||||
*/
|
||||
public interface DomainType<J> extends SqmExpressible<J> {
|
||||
@Override
|
||||
default DomainType<J> getSqmType() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,4 +22,9 @@ public interface EntityDomainType<J> extends IdentifiableDomainType<J>, EntityTy
|
|||
|
||||
@Override
|
||||
Collection<? extends EntityDomainType<? extends J>> getSubTypes();
|
||||
|
||||
@Override
|
||||
default DomainType<J> getSqmType() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,4 +16,8 @@ import jakarta.persistence.metamodel.MappedSuperclassType;
|
|||
* @author Steve Ebersole
|
||||
*/
|
||||
public interface MappedSuperclassDomainType<J> extends IdentifiableDomainType<J>, MappedSuperclassType<J>, SqmPathSource<J> {
|
||||
@Override
|
||||
default DomainType<J> getSqmType() {
|
||||
return IdentifiableDomainType.super.getSqmType();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
package org.hibernate.metamodel.model.domain.internal;
|
||||
|
||||
import org.hibernate.metamodel.model.domain.DomainType;
|
||||
import org.hibernate.metamodel.model.domain.SimpleDomainType;
|
||||
import org.hibernate.query.ReturnableType;
|
||||
import org.hibernate.query.sqm.SqmPathSource;
|
||||
|
@ -61,6 +62,11 @@ public class AnyDiscriminatorSqmPathSource<D> extends AbstractSqmPathSource<D>
|
|||
return (BasicType<D>) super.getSqmPathType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DomainType<D> getSqmType() {
|
||||
return getSqmPathType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaType<D> getExpressibleJavaType() {
|
||||
return getSqmPathType().getExpressibleJavaType();
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
package org.hibernate.metamodel.model.domain.internal;
|
||||
|
||||
import org.hibernate.metamodel.model.domain.BasicDomainType;
|
||||
import org.hibernate.metamodel.model.domain.DomainType;
|
||||
import org.hibernate.query.ReturnableType;
|
||||
import org.hibernate.query.sqm.TerminalPathException;
|
||||
import org.hibernate.query.sqm.SqmPathSource;
|
||||
|
@ -42,6 +43,11 @@ public class BasicSqmPathSource<J>
|
|||
return (BasicDomainType<J>) super.getSqmPathType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DomainType<J> getSqmType() {
|
||||
return getSqmPathType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmPathSource<?> findSubPathSource(String name) {
|
||||
String path = pathModel.getPathName();
|
||||
|
|
|
@ -60,4 +60,9 @@ public class DiscriminatorSqmPathSource<D> extends AbstractSqmPathSource<D>
|
|||
public Class<D> getJavaType() {
|
||||
return getExpressibleJavaType().getJavaTypeClass();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DomainType<D> getSqmType() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -754,8 +754,7 @@ public class MappingMetamodelImpl extends QueryParameterBindingTypeResolverImpl
|
|||
@Override
|
||||
public MappingModelExpressible<?> resolveMappingExpressible(
|
||||
SqmExpressible<?> sqmExpressible,
|
||||
Function<NavigablePath,
|
||||
TableGroup> tableGroupLocator) {
|
||||
Function<NavigablePath, TableGroup> tableGroupLocator) {
|
||||
if ( sqmExpressible instanceof SqmPath ) {
|
||||
final SqmPath<?> sqmPath = (SqmPath<?>) sqmExpressible;
|
||||
final NavigablePath navigablePath = sqmPath.getNavigablePath();
|
||||
|
|
|
@ -214,7 +214,7 @@ public class AnonymousTupleType<T> implements TupleType<T>, DomainType<T>, Retur
|
|||
else {
|
||||
return new AnonymousTupleSimpleSqmPathSource<>(
|
||||
name,
|
||||
(DomainType<? extends Object>) component.getExpressible(),
|
||||
component.getExpressible().getSqmType(),
|
||||
BindableType.SINGULAR_ATTRIBUTE
|
||||
);
|
||||
}
|
||||
|
@ -246,6 +246,11 @@ public class AnonymousTupleType<T> implements TupleType<T>, DomainType<T>, Retur
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DomainType<T> getSqmType() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmPath<T> createSqmPath(SqmPath<?> lhs, SqmPathSource<?> intermediatePathSource) {
|
||||
throw new UnsupportedMappingException(
|
||||
|
|
|
@ -971,6 +971,10 @@ public class QuerySplitter {
|
|||
|
||||
@Override
|
||||
public SqmBasicValuedSimplePath<?> visitBasicValuedPath(SqmBasicValuedSimplePath<?> path) {
|
||||
final SqmPath<?> existing = sqmPathCopyMap.get( path.getNavigablePath() );
|
||||
if ( existing != null ) {
|
||||
return (SqmBasicValuedSimplePath<?>) existing;
|
||||
}
|
||||
final SqmPathRegistry pathRegistry = getProcessingStateStack().getCurrent().getPathRegistry();
|
||||
|
||||
final SqmPath<?> lhs = findLhs( path );
|
||||
|
@ -989,6 +993,10 @@ public class QuerySplitter {
|
|||
|
||||
@Override
|
||||
public SqmEmbeddedValuedSimplePath<?> visitEmbeddableValuedPath(SqmEmbeddedValuedSimplePath<?> path) {
|
||||
final SqmPath<?> existing = sqmPathCopyMap.get( path.getNavigablePath() );
|
||||
if ( existing != null ) {
|
||||
return (SqmEmbeddedValuedSimplePath<?>) existing;
|
||||
}
|
||||
final SqmPathRegistry pathRegistry = getProcessingStateStack().getCurrent().getPathRegistry();
|
||||
final SqmPath<?> lhs = findLhs( path );
|
||||
final SqmEmbeddedValuedSimplePath<?> copy = new SqmEmbeddedValuedSimplePath<>(
|
||||
|
@ -1004,6 +1012,10 @@ public class QuerySplitter {
|
|||
|
||||
@Override
|
||||
public SqmEntityValuedSimplePath<?> visitEntityValuedPath(SqmEntityValuedSimplePath<?> path) {
|
||||
final SqmPath<?> existing = sqmPathCopyMap.get( path.getNavigablePath() );
|
||||
if ( existing != null ) {
|
||||
return (SqmEntityValuedSimplePath<?>) existing;
|
||||
}
|
||||
final SqmPathRegistry pathRegistry = getProcessingStateStack().getCurrent().getPathRegistry();
|
||||
final SqmPath<?> lhs = findLhs( path );
|
||||
final SqmEntityValuedSimplePath<?> copy = new SqmEntityValuedSimplePath<>(
|
||||
|
@ -1019,6 +1031,10 @@ public class QuerySplitter {
|
|||
|
||||
@Override
|
||||
public SqmPluralValuedSimplePath<?> visitPluralValuedPath(SqmPluralValuedSimplePath<?> path) {
|
||||
final SqmPath<?> existing = sqmPathCopyMap.get( path.getNavigablePath() );
|
||||
if ( existing != null ) {
|
||||
return (SqmPluralValuedSimplePath<?>) existing;
|
||||
}
|
||||
final SqmPathRegistry pathRegistry = getProcessingStateStack().getCurrent().getPathRegistry();
|
||||
SqmPath<?> lhs = findLhs( path );
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import java.time.temporal.TemporalAccessor;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
@ -124,6 +125,7 @@ import org.hibernate.query.sqm.tree.domain.SqmMapJoin;
|
|||
import org.hibernate.query.sqm.tree.domain.SqmPath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmPolymorphicRootDescriptor;
|
||||
import org.hibernate.query.sqm.tree.expression.AbstractSqmParameter;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmAliasedNodeRef;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmAny;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmAnyDiscriminatorValue;
|
||||
|
@ -313,6 +315,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
|||
|
||||
private ParameterCollector parameterCollector;
|
||||
private ParameterStyle parameterStyle;
|
||||
private Map<Object, AbstractSqmParameter<?>> parameters;
|
||||
|
||||
private boolean isExtractingJdbcTemporalType;
|
||||
// Provides access to the current CTE that is being processed, which is potentially recursive
|
||||
|
@ -3839,14 +3842,14 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
|||
HqlParser.NamedParameterContext ctx,
|
||||
SqmExpressible<T> expressibleType) {
|
||||
parameterStyle = parameterStyle.withNamed();
|
||||
final SqmNamedParameter<T> param = new SqmNamedParameter<>(
|
||||
ctx.getChild( 1 ).getText(),
|
||||
parameterDeclarationContextStack.getCurrent().isMultiValuedBindingAllowed(),
|
||||
expressibleType,
|
||||
creationContext.getNodeBuilder()
|
||||
return resolveParameter(
|
||||
new SqmNamedParameter<>(
|
||||
ctx.getChild( 1 ).getText(),
|
||||
parameterDeclarationContextStack.getCurrent().isMultiValuedBindingAllowed(),
|
||||
expressibleType,
|
||||
creationContext.getNodeBuilder()
|
||||
)
|
||||
);
|
||||
parameterCollector.addParameter( param );
|
||||
return param;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -3861,14 +3864,28 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
|||
throw new ParameterLabelException( "Unlabeled ordinal parameter ('?' rather than ?1)" );
|
||||
}
|
||||
parameterStyle = parameterStyle.withPositional();
|
||||
final SqmPositionalParameter<T> param = new SqmPositionalParameter<>(
|
||||
Integer.parseInt( ctx.getChild( 1 ).getText() ),
|
||||
parameterDeclarationContextStack.getCurrent().isMultiValuedBindingAllowed(),
|
||||
expressibleType,
|
||||
creationContext.getNodeBuilder()
|
||||
return resolveParameter(
|
||||
new SqmPositionalParameter<>(
|
||||
Integer.parseInt( ctx.getChild( 1 ).getText() ),
|
||||
parameterDeclarationContextStack.getCurrent().isMultiValuedBindingAllowed(),
|
||||
expressibleType,
|
||||
creationContext.getNodeBuilder()
|
||||
)
|
||||
);
|
||||
parameterCollector.addParameter( param );
|
||||
return param;
|
||||
}
|
||||
|
||||
private <T extends AbstractSqmParameter<?>> T resolveParameter(T parameter) {
|
||||
if ( parameters == null ) {
|
||||
parameters = new HashMap<>();
|
||||
}
|
||||
final Object key = parameter.getName() == null ? parameter.getPosition() : parameter.getName();
|
||||
final AbstractSqmParameter<?> existingParameter = parameters.putIfAbsent( key, parameter );
|
||||
if ( existingParameter == null ) {
|
||||
parameterCollector.addParameter( parameter );
|
||||
return parameter;
|
||||
}
|
||||
//noinspection unchecked
|
||||
return (T) existingParameter;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
package org.hibernate.query.sqm;
|
||||
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.metamodel.model.domain.DomainType;
|
||||
import org.hibernate.query.BindableType;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmExpression;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
|
@ -51,4 +52,6 @@ public interface SqmExpressible<J> extends BindableType<J> {
|
|||
JavaType<J> expressibleJavaType = getExpressibleJavaType();
|
||||
return expressibleJavaType == null ? "unknown" : expressibleJavaType.getJavaType().getTypeName();
|
||||
}
|
||||
|
||||
DomainType<J> getSqmType();
|
||||
}
|
||||
|
|
|
@ -84,6 +84,11 @@ public interface SqmPathSource<J> extends SqmExpressible<J>, Bindable<J>, SqmExp
|
|||
return (SqmExpressible<J>) getSqmPathType();
|
||||
}
|
||||
|
||||
@Override
|
||||
default DomainType<J> getSqmType() {
|
||||
return (DomainType<J>) getSqmPathType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this path source is generically typed
|
||||
*/
|
||||
|
|
|
@ -1289,6 +1289,11 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
|
|||
public Class<T> getBindableJavaType() {
|
||||
return javaType.getJavaTypeClass();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DomainType<T> getSqmType() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1829,8 +1834,8 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
|
|||
public <Y> JpaCoalesce<Y> coalesce(Expression<? extends Y> x, Expression<? extends Y> y) {
|
||||
@SuppressWarnings("unchecked")
|
||||
final SqmExpressible<Y> sqmExpressible = (SqmExpressible<Y>) highestPrecedenceType(
|
||||
( (SqmExpression<? extends Y>) x ).getNodeType(),
|
||||
( (SqmExpression<? extends Y>) y ).getNodeType()
|
||||
( (SqmExpression<? extends Y>) x ).getExpressible(),
|
||||
( (SqmExpression<? extends Y>) y ).getExpressible()
|
||||
);
|
||||
return new SqmCoalesce<>(
|
||||
sqmExpressible,
|
||||
|
@ -1860,9 +1865,9 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
|
|||
private <Y> SqmExpression<Y> createNullifFunctionNode(SqmExpression<Y> first, SqmExpression<Y> second) {
|
||||
//noinspection unchecked
|
||||
final ReturnableType<Y> type = (ReturnableType<Y>) highestPrecedenceType(
|
||||
first.getNodeType(),
|
||||
second.getNodeType()
|
||||
);
|
||||
first.getExpressible(),
|
||||
second.getExpressible()
|
||||
).getSqmType();
|
||||
|
||||
return getFunctionDescriptor("nullif").generateSqmExpression(
|
||||
asList( first, second ),
|
||||
|
|
|
@ -32,8 +32,10 @@ import org.hibernate.query.sqm.SqmExpressible;
|
|||
import org.hibernate.query.sqm.SqmPathSource;
|
||||
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
|
||||
import org.hibernate.query.sqm.tree.SqmTypedNode;
|
||||
import org.hibernate.query.sqm.tree.cte.SqmCteTable;
|
||||
import org.hibernate.query.sqm.tree.domain.AbstractSqmSpecificPluralPartPath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmPath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmPolymorphicRootDescriptor;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmTreatedPath;
|
||||
import org.hibernate.spi.NavigablePath;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
|
@ -211,13 +213,34 @@ public class SqmMappingModelHelper {
|
|||
}
|
||||
|
||||
if ( sqmPath.getLhs() == null ) {
|
||||
final EntityDomainType<?> entityDomainType = (EntityDomainType<?>) sqmPath.getReferencedPathSource();
|
||||
return domainModel.findEntityDescriptor( entityDomainType.getHibernateEntityName() );
|
||||
final SqmPathSource<?> referencedPathSource = sqmPath.getReferencedPathSource();
|
||||
if ( referencedPathSource instanceof EntityDomainType<?> ) {
|
||||
final EntityDomainType<?> entityDomainType = (EntityDomainType<?>) referencedPathSource;
|
||||
return domainModel.findEntityDescriptor( entityDomainType.getHibernateEntityName() );
|
||||
}
|
||||
assert referencedPathSource instanceof SqmCteTable<?>;
|
||||
return null;
|
||||
}
|
||||
final TableGroup lhsTableGroup = tableGroupLocator.apply( sqmPath.getLhs().getNavigablePath() );
|
||||
final ModelPartContainer modelPart;
|
||||
if ( lhsTableGroup == null ) {
|
||||
modelPart = (ModelPartContainer) resolveSqmPath( sqmPath.getLhs(), domainModel, tableGroupLocator );
|
||||
if ( modelPart == null ) {
|
||||
// There are many reasons for why this situation can happen,
|
||||
// but they all boil down to a parameter being compared against a SqmPath.
|
||||
|
||||
// * If the parameter is used in multiple queries (CTE or subquery),
|
||||
// resolving the parameter type based on a SqmPath from a query context other than the current one will fail.
|
||||
|
||||
// * If the parameter is compared to paths with a polymorphic root,
|
||||
// the parameter has a SqmPath set as SqmExpressible
|
||||
// which is still referring to the polymorphic navigable path,
|
||||
// but during query splitting, the SqmRoot in the query is replaced with a root for a subtype.
|
||||
// Unfortunately, we can't copy the parameter to reset the SqmExpressible,
|
||||
// because we currently build only a single DomainParameterXref, instead of one per query split,
|
||||
// so we have to handle this here instead
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
modelPart = lhsTableGroup.getModelPart();
|
||||
|
|
|
@ -257,6 +257,7 @@ public class SqmUtil {
|
|||
final Iterator<?> valueItr = bindValues.iterator();
|
||||
|
||||
// the original SqmParameter is the one we are processing.. create a binding for it..
|
||||
final Object firstValue = valueItr.next();
|
||||
for ( int i = 0; i < jdbcParamsBinds.size(); i++ ) {
|
||||
final JdbcParametersList jdbcParams = jdbcParamsBinds.get( i );
|
||||
createValueBindings(
|
||||
|
@ -265,7 +266,7 @@ public class SqmUtil {
|
|||
domainParamBinding,
|
||||
parameterType,
|
||||
jdbcParams,
|
||||
valueItr.next(),
|
||||
firstValue,
|
||||
tableGroupLocator,
|
||||
session
|
||||
);
|
||||
|
@ -273,23 +274,30 @@ public class SqmUtil {
|
|||
|
||||
// an then one for each of the expansions
|
||||
final List<SqmParameter<?>> expansions = domainParameterXref.getExpansions( sqmParameter );
|
||||
assert expansions.size() == bindValues.size() - 1;
|
||||
final int expansionCount = bindValues.size() - 1;
|
||||
final int parameterUseCount = jdbcParamsBinds.size();
|
||||
assert expansions.size() == expansionCount * parameterUseCount;
|
||||
int expansionPosition = 0;
|
||||
while ( valueItr.hasNext() ) {
|
||||
final SqmParameter<?> expansionSqmParam = expansions.get( expansionPosition++ );
|
||||
final List<JdbcParametersList> jdbcParamBinds = jdbcParamMap.get( expansionSqmParam );
|
||||
for ( int i = 0; i < jdbcParamBinds.size(); i++ ) {
|
||||
JdbcParametersList expansionJdbcParams = jdbcParamBinds.get( i );
|
||||
createValueBindings(
|
||||
jdbcParameterBindings,
|
||||
queryParam, domainParamBinding,
|
||||
parameterType,
|
||||
expansionJdbcParams,
|
||||
valueItr.next(),
|
||||
tableGroupLocator,
|
||||
session
|
||||
);
|
||||
final Object expandedValue = valueItr.next();
|
||||
for ( int j = 0; j < parameterUseCount; j++ ) {
|
||||
final SqmParameter<?> expansionSqmParam = expansions.get( expansionPosition + j * expansionCount );
|
||||
final List<JdbcParametersList> jdbcParamBinds = jdbcParamMap.get( expansionSqmParam );
|
||||
for ( int i = 0; i < jdbcParamBinds.size(); i++ ) {
|
||||
JdbcParametersList expansionJdbcParams = jdbcParamBinds.get( i );
|
||||
createValueBindings(
|
||||
jdbcParameterBindings,
|
||||
queryParam,
|
||||
domainParamBinding,
|
||||
parameterType,
|
||||
expansionJdbcParams,
|
||||
expandedValue,
|
||||
tableGroupLocator,
|
||||
session
|
||||
);
|
||||
}
|
||||
}
|
||||
expansionPosition++;
|
||||
}
|
||||
}
|
||||
else if ( domainParamBinding.getBindValue() == null ) {
|
||||
|
|
|
@ -5594,11 +5594,14 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
if ( sqmExpression instanceof SqmPath ) {
|
||||
log.debugf( "Determining mapping-model type for SqmPath : %s ", sqmExpression );
|
||||
|
||||
return SqmMappingModelHelper.resolveMappingModelExpressible(
|
||||
final MappingModelExpressible<?> mappingModelExpressible = SqmMappingModelHelper.resolveMappingModelExpressible(
|
||||
sqmExpression,
|
||||
domainModel,
|
||||
fromClauseIndex::findTableGroup
|
||||
);
|
||||
if ( mappingModelExpressible != null ) {
|
||||
return mappingModelExpressible;
|
||||
}
|
||||
}
|
||||
|
||||
if ( sqmExpression instanceof SqmBooleanExpressionPredicate ) {
|
||||
|
@ -5759,19 +5762,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
final SqmExpressible<?> paramSqmType = paramType.resolveExpressible( creationContext.getSessionFactory() );
|
||||
|
||||
if ( paramSqmType instanceof SqmPath ) {
|
||||
final SqmPath<?> sqmPath = (SqmPath<?>) paramSqmType;
|
||||
final NavigablePath navigablePath = sqmPath.getNavigablePath();
|
||||
final ModelPart modelPart;
|
||||
if ( navigablePath.getParent() != null ) {
|
||||
final TableGroup tableGroup = getFromClauseAccess().getTableGroup( navigablePath.getParent() );
|
||||
modelPart = tableGroup.getModelPart().findSubPart(
|
||||
navigablePath.getLocalName(),
|
||||
null
|
||||
);
|
||||
}
|
||||
else {
|
||||
modelPart = getFromClauseAccess().getTableGroup( navigablePath ).getModelPart();
|
||||
}
|
||||
final MappingModelExpressible<?> modelPart = determineValueMapping( (SqmPath<?>) paramSqmType );
|
||||
if ( modelPart instanceof PluralAttributeMapping ) {
|
||||
return resolveInferredValueMappingForParameter( ( (PluralAttributeMapping) modelPart ).getElementDescriptor() );
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
package org.hibernate.query.sqm.tree.domain;
|
||||
|
||||
import org.hibernate.metamodel.model.domain.DomainType;
|
||||
import org.hibernate.metamodel.model.domain.EntityDomainType;
|
||||
import org.hibernate.query.sqm.UnknownPathException;
|
||||
import org.hibernate.spi.NavigablePath;
|
||||
|
@ -63,6 +64,11 @@ public class SqmBasicValuedSimplePath<T>
|
|||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpressible<T> getExpressible() {
|
||||
return this;
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// SemanticPathPart
|
||||
|
||||
|
@ -123,6 +129,11 @@ public class SqmBasicValuedSimplePath<T>
|
|||
return getJavaType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DomainType<T> getSqmType() {
|
||||
return getNodeType().getSqmType();
|
||||
}
|
||||
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Visitation
|
||||
|
|
|
@ -90,6 +90,11 @@ public class SqmCteRoot<T> extends SqmRoot<T> implements JpaRoot<T> {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEntityName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmPathSource<?> getResolvedModel() {
|
||||
return getReferencedPathSource();
|
||||
|
|
|
@ -92,6 +92,11 @@ public class SqmDerivedRoot<T> extends SqmRoot<T> implements JpaDerivedRoot<T> {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEntityName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmPathSource<?> getResolvedModel() {
|
||||
return getReferencedPathSource();
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
package org.hibernate.query.sqm.tree.domain;
|
||||
|
||||
import org.hibernate.metamodel.model.domain.DomainType;
|
||||
import org.hibernate.metamodel.model.domain.EmbeddableDomainType;
|
||||
import org.hibernate.metamodel.model.domain.EntityDomainType;
|
||||
import org.hibernate.query.PathException;
|
||||
|
@ -68,6 +69,16 @@ public class SqmEmbeddedValuedSimplePath<T>
|
|||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpressible<T> getExpressible() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DomainType<T> getSqmType() {
|
||||
return getReferencedPathSource().getSqmType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmPath<?> resolvePathPart(
|
||||
String name,
|
||||
|
|
|
@ -12,6 +12,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.hibernate.metamodel.model.domain.DomainType;
|
||||
import org.hibernate.query.criteria.JpaSelection;
|
||||
import org.hibernate.query.sqm.NodeBuilder;
|
||||
import org.hibernate.query.sqm.SemanticQueryWalker;
|
||||
|
@ -120,6 +121,11 @@ public class SqmMapEntryReference<K,V>
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DomainType<Map.Entry<K, V>> getSqmType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Map.Entry<K, V>> getBindableJavaType() {
|
||||
return getNodeType().getBindableJavaType();
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.hibernate.type.descriptor.java.JavaType;
|
|||
import jakarta.persistence.criteria.Expression;
|
||||
|
||||
import static org.hibernate.query.internal.QueryHelper.highestPrecedenceType;
|
||||
import static org.hibernate.query.internal.QueryHelper.highestPrecedenceType2;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
|
@ -57,11 +58,11 @@ public abstract class AbstractSqmExpression<T> extends AbstractJpaSelection<T> i
|
|||
SqmTreeCreationLogger.LOGGER.debugf(
|
||||
"Applying inferable type to SqmExpression [%s] : %s -> %s",
|
||||
this,
|
||||
getNodeType(),
|
||||
getExpressible(),
|
||||
newType
|
||||
);
|
||||
|
||||
setExpressibleType( highestPrecedenceType( newType, getNodeType() ) );
|
||||
setExpressibleType( highestPrecedenceType2( newType, getExpressible() ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -36,12 +36,7 @@ public abstract class AbstractSqmParameter<T> extends AbstractSqmExpression<T> i
|
|||
else if ( type instanceof PluralPersistentAttribute<?, ?, ?> ) {
|
||||
type = ( (PluralPersistentAttribute<?, ?, ?>) type ).getElementType();
|
||||
}
|
||||
final SqmExpressible<T> oldType = getNodeType();
|
||||
|
||||
final SqmExpressible<?> newType = QueryHelper.highestPrecedenceType( oldType, type );
|
||||
if ( newType != null && newType != oldType ) {
|
||||
internalApplyInferableType( newType );
|
||||
}
|
||||
internalApplyInferableType( type );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
package org.hibernate.query.sqm.tree.expression;
|
||||
|
||||
import org.hibernate.metamodel.model.domain.DomainType;
|
||||
import org.hibernate.query.sqm.SqmExpressible;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
|
||||
|
@ -27,4 +28,9 @@ public class NullSqmExpressible implements SqmExpressible<Object> {
|
|||
public JavaType<Object> getExpressibleJavaType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DomainType<Object> getSqmType() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,7 +96,7 @@ public class SqmCaseSearched<R>
|
|||
return;
|
||||
}
|
||||
|
||||
final SqmExpressible<?> oldType = getNodeType();
|
||||
final SqmExpressible<?> oldType = getExpressible();
|
||||
|
||||
final SqmExpressible<?> newType = QueryHelper.highestPrecedenceType2( oldType, type );
|
||||
if ( newType != null && newType != oldType ) {
|
||||
|
|
|
@ -104,9 +104,9 @@ public class SqmCaseSimple<T, R>
|
|||
return;
|
||||
}
|
||||
|
||||
final SqmExpressible<?> oldType = getNodeType();
|
||||
final SqmExpressible<?> oldType = getExpressible();
|
||||
|
||||
final SqmExpressible<?> newType = QueryHelper.highestPrecedenceType2(oldType, type );
|
||||
final SqmExpressible<?> newType = QueryHelper.highestPrecedenceType2( oldType, type );
|
||||
if ( newType != null && newType != oldType ) {
|
||||
internalApplyInferableType( newType );
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import java.math.BigDecimal;
|
|||
import java.math.BigInteger;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.hibernate.metamodel.model.domain.DomainType;
|
||||
import org.hibernate.query.hql.spi.SemanticPathPart;
|
||||
import org.hibernate.query.hql.spi.SqmCreationState;
|
||||
import org.hibernate.query.sqm.NodeBuilder;
|
||||
|
@ -26,7 +27,7 @@ import org.hibernate.type.descriptor.java.EnumJavaType;
|
|||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class SqmEnumLiteral<E extends Enum<E>> extends AbstractSqmExpression<E> implements SqmExpressible<E>, SemanticPathPart {
|
||||
public class SqmEnumLiteral<E extends Enum<E>> extends SqmLiteral<E> implements SqmExpressible<E>, SemanticPathPart {
|
||||
private final E enumValue;
|
||||
private final EnumJavaType<E> referencedEnumTypeDescriptor;
|
||||
private final String enumValueName;
|
||||
|
@ -62,6 +63,16 @@ public class SqmEnumLiteral<E extends Enum<E>> extends AbstractSqmExpression<E>
|
|||
return expression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpressible<E> getExpressible() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DomainType<E> getSqmType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public E getEnumValue() {
|
||||
return enumValue;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import java.util.List;
|
|||
import java.util.Locale;
|
||||
|
||||
import org.hibernate.QueryException;
|
||||
import org.hibernate.metamodel.model.domain.DomainType;
|
||||
import org.hibernate.query.criteria.JpaSelection;
|
||||
import org.hibernate.query.hql.spi.SemanticPathPart;
|
||||
import org.hibernate.query.hql.spi.SqmCreationState;
|
||||
|
@ -275,4 +276,9 @@ public class SqmFieldLiteral<T> implements SqmExpression<T>, SqmExpressible<T>,
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DomainType<T> getSqmType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -33,9 +33,9 @@ public class SqmBetweenPredicate extends AbstractNegatableSqmPredicate {
|
|||
this.upperBound = upperBound;
|
||||
|
||||
final SqmExpressible<?> expressibleType = QueryHelper.highestPrecedenceType(
|
||||
expression.getNodeType(),
|
||||
lowerBound.getNodeType(),
|
||||
upperBound.getNodeType()
|
||||
expression.getExpressible(),
|
||||
lowerBound.getExpressible(),
|
||||
upperBound.getExpressible()
|
||||
);
|
||||
|
||||
expression.applyInferableType( expressibleType );
|
||||
|
|
|
@ -42,8 +42,8 @@ public class SqmComparisonPredicate extends AbstractNegatableSqmPredicate {
|
|||
this.operator = operator;
|
||||
|
||||
final SqmExpressible<?> expressibleType = QueryHelper.highestPrecedenceType(
|
||||
leftHandExpression.getNodeType(),
|
||||
rightHandExpression.getNodeType()
|
||||
leftHandExpression.getExpressible(),
|
||||
rightHandExpression.getExpressible()
|
||||
);
|
||||
|
||||
leftHandExpression.applyInferableType( expressibleType );
|
||||
|
|
|
@ -137,7 +137,7 @@ public class SqmInListPredicate<T> extends AbstractNegatableSqmPredicate impleme
|
|||
private void implyListElementType(SqmExpression<?> expression) {
|
||||
nodeBuilder().assertComparable( getTestExpression(), expression );
|
||||
expression.applyInferableType(
|
||||
QueryHelper.highestPrecedenceType2( getTestExpression().getNodeType(), expression.getNodeType() )
|
||||
QueryHelper.highestPrecedenceType2( getTestExpression().getExpressible(), expression.getExpressible() )
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -41,8 +41,8 @@ public class SqmInSubQueryPredicate<T> extends AbstractNegatableSqmPredicate imp
|
|||
this.subQueryExpression = subQueryExpression;
|
||||
|
||||
final SqmExpressible<?> expressibleType = QueryHelper.highestPrecedenceType2(
|
||||
testExpression.getNodeType(),
|
||||
subQueryExpression.getNodeType()
|
||||
testExpression.getExpressible(),
|
||||
subQueryExpression.getExpressible()
|
||||
);
|
||||
|
||||
testExpression.applyInferableType( expressibleType );
|
||||
|
|
|
@ -52,8 +52,8 @@ public class SqmLikePredicate extends AbstractNegatableSqmPredicate {
|
|||
this.escapeCharacter = escapeCharacter;
|
||||
this.isCaseSensitive = isCaseSensitive;
|
||||
final SqmExpressible<?> expressibleType = QueryHelper.highestPrecedenceType(
|
||||
matchExpression.getNodeType(),
|
||||
pattern.getNodeType()
|
||||
matchExpression.getExpressible(),
|
||||
pattern.getExpressible()
|
||||
);
|
||||
matchExpression.applyInferableType( expressibleType );
|
||||
pattern.applyInferableType( expressibleType );
|
||||
|
|
|
@ -11,6 +11,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.hibernate.metamodel.model.domain.DomainType;
|
||||
import org.hibernate.query.sqm.DynamicInstantiationNature;
|
||||
import org.hibernate.query.criteria.JpaCompoundSelection;
|
||||
import org.hibernate.query.sqm.NodeBuilder;
|
||||
|
@ -265,6 +266,11 @@ public class SqmDynamicInstantiation<T>
|
|||
public Class<T> getBindableJavaType() {
|
||||
return getTargetTypeDescriptor().getJavaTypeClass();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DomainType<T> getSqmType() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.hibernate.metamodel.model.domain.DomainType;
|
||||
import org.hibernate.query.criteria.JpaCompoundSelection;
|
||||
import org.hibernate.query.criteria.JpaSelection;
|
||||
import org.hibernate.query.sqm.NodeBuilder;
|
||||
|
@ -149,4 +150,9 @@ public class SqmJpaCompoundSelection<T>
|
|||
public void visitSubSelectableNodes(Consumer<SqmSelectableNode<?>> jpaSelectionConsumer) {
|
||||
selectableNodes.forEach( jpaSelectionConsumer );
|
||||
}
|
||||
|
||||
@Override
|
||||
public DomainType<T> getSqmType() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,221 @@
|
|||
/*
|
||||
* 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.orm.test.mapping.converted.enums;
|
||||
|
||||
import org.hibernate.type.descriptor.JdbcBindingLogging;
|
||||
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.JiraKey;
|
||||
import org.hibernate.testing.orm.junit.Logger;
|
||||
import org.hibernate.testing.orm.junit.MessageKeyInspection;
|
||||
import org.hibernate.testing.orm.junit.MessageKeyWatcher;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@MessageKeyInspection(
|
||||
logger = @Logger( loggerName = JdbcBindingLogging.NAME ),
|
||||
messageKey = "binding parameter ["
|
||||
)
|
||||
@DomainModel( annotatedClasses = VarcharEnumTypeTest.Person.class )
|
||||
@SessionFactory
|
||||
public class VarcharEnumTypeTest {
|
||||
@BeforeEach
|
||||
protected void createTestData(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
(session) -> {
|
||||
final Person person = Person.person( Gender.MALE, HairColor.BROWN );
|
||||
session.persist( person );
|
||||
session.persist( Person.person( Gender.MALE, HairColor.BLACK ) );
|
||||
session.persist( Person.person( Gender.FEMALE, HairColor.BROWN ) );
|
||||
session.persist( Person.person( Gender.FEMALE, HairColor.BLACK ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void dropTestData(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
(session) -> session.createQuery( "delete Person" ).executeUpdate()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@JiraKey("HHH-12978")
|
||||
public void testEnumAsBindParameterAndExtract(SessionFactoryScope scope, MessageKeyWatcher loggingWatcher) {
|
||||
scope.inTransaction(
|
||||
(session) -> {
|
||||
session.createQuery( "select p.id from Person p where p.id = :id", Long.class )
|
||||
.setParameter( "id", 1L )
|
||||
.list();
|
||||
|
||||
assertTrue( loggingWatcher.wasTriggered() );
|
||||
}
|
||||
);
|
||||
|
||||
loggingWatcher.reset();
|
||||
|
||||
scope.inTransaction(
|
||||
(session) -> {
|
||||
final String qry = "select p.gender from Person p where p.gender = :gender and p.hairColor = :hairColor";
|
||||
session.createQuery( qry, Gender.class )
|
||||
.setParameter( "gender", Gender.MALE )
|
||||
.setParameter( "hairColor", HairColor.BROWN )
|
||||
.getSingleResult();
|
||||
|
||||
assertTrue( loggingWatcher.wasTriggered() );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@JiraKey("HHH-10282")
|
||||
public void hqlTestEnumShortHandSyntax(SessionFactoryScope scope, MessageKeyWatcher loggingWatcher) {
|
||||
scope.inTransaction(
|
||||
(session) -> {
|
||||
session.createQuery(
|
||||
"select id from Person where originalHairColor = BLONDE")
|
||||
.getResultList();
|
||||
|
||||
assertTrue( loggingWatcher.wasTriggered() );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@JiraKey("HHH-10282")
|
||||
public void hqlTestEnumQualifiedShortHandSyntax(SessionFactoryScope scope, MessageKeyWatcher loggingWatcher) {
|
||||
final String qry = "select id from Person where originalHairColor = HairColor.BLONDE";
|
||||
scope.inTransaction(
|
||||
(session) -> {
|
||||
session.createQuery( qry ).getResultList();
|
||||
|
||||
assertTrue( loggingWatcher.wasTriggered() );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@JiraKey("HHH-10282")
|
||||
public void hqlTestEnumShortHandSyntaxInPredicate(SessionFactoryScope scope, MessageKeyWatcher loggingWatcher) {
|
||||
scope.inTransaction(
|
||||
(session) -> {
|
||||
final String qry = "select id from Person where originalHairColor in (BLONDE, BROWN)";
|
||||
session.createQuery( qry ).getResultList();
|
||||
|
||||
assertTrue( loggingWatcher.wasTriggered() );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@JiraKey("HHH-10282")
|
||||
public void hqlTestEnumQualifiedShortHandSyntaxInPredicate(SessionFactoryScope scope, MessageKeyWatcher loggingWatcher) {
|
||||
scope.inTransaction(
|
||||
(session) -> {
|
||||
final String qry = "select id from Person where originalHairColor in (HairColor.BLONDE, HairColor.BROWN)";
|
||||
session.createQuery( qry ).getResultList();
|
||||
|
||||
assertTrue( loggingWatcher.wasTriggered() );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@JiraKey("HHH-16739")
|
||||
public void testCompareEnumParameterWithDifferentTypedExpressions(SessionFactoryScope scope) {
|
||||
scope.inSession(
|
||||
s -> {
|
||||
s.createQuery( "select p.id from Person p where p.gender = :gender and :gender = 'MALE'", Long.class )
|
||||
.setParameter( "gender", Gender.MALE )
|
||||
.getResultList();
|
||||
s.createQuery( "select p.id from Person p where p.gender = :gender and :gender = org.hibernate.orm.test.mapping.converted.enums.Gender.MALE", Long.class )
|
||||
.setParameter( "gender", Gender.MALE )
|
||||
.getResultList();
|
||||
|
||||
s.createQuery( "select p.id from Person p where :gender = org.hibernate.orm.test.mapping.converted.enums.Gender.MALE and p.gender = :gender", Long.class )
|
||||
.setParameter( "gender", Gender.MALE )
|
||||
.getResultList();
|
||||
|
||||
s.createQuery( "select p.id from Person p where :gender = 'MALE' and p.gender = :gender", Long.class )
|
||||
.setParameter( "gender", Gender.MALE )
|
||||
.getResultList();
|
||||
|
||||
s.createQuery( "select p.id from Person p where :gender = 'MALE' or :gender = 'FEMALE' and p.gender = :gender", Long.class )
|
||||
.setParameter( "gender", Gender.MALE )
|
||||
.getResultList();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Entity(name = "Person")
|
||||
public static class Person {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long id;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
private Gender gender;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
private HairColor hairColor;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
private HairColor originalHairColor;
|
||||
|
||||
public static Person person(Gender gender, HairColor hairColor) {
|
||||
Person person = new Person();
|
||||
person.setGender( gender );
|
||||
person.setHairColor( hairColor );
|
||||
return person;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Gender getGender() {
|
||||
return gender;
|
||||
}
|
||||
|
||||
public void setGender(Gender gender) {
|
||||
this.gender = gender;
|
||||
}
|
||||
|
||||
public HairColor getHairColor() {
|
||||
return hairColor;
|
||||
}
|
||||
|
||||
public void setHairColor(HairColor hairColor) {
|
||||
this.hairColor = hairColor;
|
||||
}
|
||||
|
||||
public HairColor getOriginalHairColor() {
|
||||
return originalHairColor;
|
||||
}
|
||||
|
||||
public void setOriginalHairColor(HairColor originalHairColor) {
|
||||
this.originalHairColor = originalHairColor;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue