resolve an issue with ArgumentsValidator and access to the MappingMetamodel

(ideally we should never access the MappingMetamodel from ArgumentsValidator)
This commit is contained in:
Gavin 2023-01-04 13:37:29 +01:00 committed by Gavin King
parent e3ed3028c4
commit 196d7a1b5a
10 changed files with 109 additions and 92 deletions

View File

@ -6,20 +6,19 @@
*/
package org.hibernate.community.dialect;
import java.util.Collections;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.engine.query.internal.NativeQueryInterpreterStandardImpl;
import org.hibernate.metamodel.model.domain.internal.JpaMetamodelImpl;
import org.hibernate.metamodel.model.domain.internal.MappingMetamodelImpl;
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
import org.hibernate.query.criteria.ValueHandlingMode;
import org.hibernate.query.internal.NamedObjectRepositoryImpl;
import org.hibernate.query.spi.QueryEngine;
import org.hibernate.query.sqm.function.SelfRenderingSqmFunction;
import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.spi.StringBuilderSqlAppender;
import org.hibernate.testing.boot.MetadataBuildingContextTestingImpl;
import org.hibernate.type.BasicType;
import org.hibernate.type.descriptor.java.JdbcDateJavaType;
import org.hibernate.type.descriptor.java.JdbcTimestampJavaType;
@ -33,6 +32,9 @@ import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static org.hibernate.engine.query.internal.NativeQueryInterpreterStandardImpl.NATIVE_QUERY_INTERPRETER;
import static org.junit.Assert.assertEquals;
/**
@ -43,15 +45,18 @@ import static org.junit.Assert.assertEquals;
public class InformixDialectTestCase extends BaseUnitTestCase {
private static final InformixDialect dialect = new InformixDialect();
private static ServiceRegistry ssr;
private static StandardServiceRegistry ssr;
private static QueryEngine queryEngine;
private static MappingMetamodelImplementor mappingMetamodel;
private static TypeConfiguration typeConfiguration;
@BeforeClass
public static void init() {
TypeConfiguration typeConfiguration = new TypeConfiguration();
final JpaMetamodelImpl jpaMetamodel = new JpaMetamodelImpl( typeConfiguration, new MappingMetamodelImpl( typeConfiguration, ssr ), ssr );
ssr = new StandardServiceRegistryBuilder().build();
typeConfiguration = new TypeConfiguration();
typeConfiguration.scope( new MetadataBuildingContextTestingImpl( ssr ) );
mappingMetamodel = new MappingMetamodelImpl( typeConfiguration, ssr );
final JpaMetamodelImpl jpaMetamodel = new JpaMetamodelImpl( typeConfiguration, mappingMetamodel, ssr );
queryEngine = new QueryEngine(
null,
null,
@ -59,8 +64,8 @@ public class InformixDialectTestCase extends BaseUnitTestCase {
ValueHandlingMode.BIND,
dialect.getPreferredSqlTypeCodeForBoolean(),
false,
new NamedObjectRepositoryImpl( Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap() ),
NativeQueryInterpreterStandardImpl.NATIVE_QUERY_INTERPRETER,
new NamedObjectRepositoryImpl( emptyMap(), emptyMap(), emptyMap(), emptyMap() ),
NATIVE_QUERY_INTERPRETER,
dialect,
ssr
);
@ -89,17 +94,14 @@ public class InformixDialectTestCase extends BaseUnitTestCase {
public void testCurrentTimestampFunction() {
SqmFunctionDescriptor functionDescriptor = queryEngine.getSqmFunctionRegistry()
.findFunctionDescriptor( "current_timestamp" );
SelfRenderingSqmFunction<Object> sqmExpression = functionDescriptor.generateSqmExpression(
null,
queryEngine,
new TypeConfiguration()
);
SelfRenderingSqmFunction<Object> sqmExpression =
functionDescriptor.generateSqmExpression( null, queryEngine, typeConfiguration );
BasicType<?> basicType = (BasicType<?>) sqmExpression.getNodeType();
assertEquals( JdbcTimestampJavaType.INSTANCE, basicType.getJavaTypeDescriptor() );
assertEquals( TimestampJdbcType.INSTANCE, basicType.getJdbcType() );
SqlAppender appender = new StringBuilderSqlAppender();
sqmExpression.getRenderingSupport().render( appender, Collections.emptyList(), null );
sqmExpression.getRenderingSupport().render( appender, emptyList(), null );
assertEquals( "current", appender.toString() );
}
@ -108,17 +110,14 @@ public class InformixDialectTestCase extends BaseUnitTestCase {
public void testCurrentDateFunction() {
SqmFunctionDescriptor functionDescriptor = queryEngine.getSqmFunctionRegistry()
.findFunctionDescriptor( "current_date" );
SelfRenderingSqmFunction<Object> sqmExpression = functionDescriptor.generateSqmExpression(
null,
queryEngine,
new TypeConfiguration()
);
SelfRenderingSqmFunction<Object> sqmExpression =
functionDescriptor.generateSqmExpression( null, queryEngine, typeConfiguration );
BasicType<?> basicType = (BasicType<?>) sqmExpression.getNodeType();
assertEquals( JdbcDateJavaType.INSTANCE, basicType.getJavaTypeDescriptor() );
assertEquals( DateJdbcType.INSTANCE, basicType.getJdbcType() );
SqlAppender appender = new StringBuilderSqlAppender();
sqmExpression.getRenderingSupport().render( appender, Collections.emptyList(), null );
sqmExpression.getRenderingSupport().render( appender, emptyList(), null );
assertEquals( "today", appender.toString() );
}

View File

@ -10,7 +10,6 @@ import java.util.List;
import java.util.Locale;
import org.hibernate.QueryException;
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
import org.hibernate.query.sqm.produce.function.ArgumentsValidator;
@ -43,7 +42,7 @@ public class ChrLiteralEmulation extends AbstractSqmSelfRenderingFunctionDescrip
StandardArgumentsValidators.exactly(1),
new ArgumentsValidator() {
@Override
public void validate(List<? extends SqmTypedNode<?>> arguments, String functionName, MappingMetamodel metamodel) {
public void validate(List<? extends SqmTypedNode<?>> arguments, String functionName, TypeConfiguration typeConfiguration) {
if ( !( arguments.get( 0 ) instanceof SqmLiteral<?> ) ) {
throw new QueryException(
String.format(

View File

@ -47,10 +47,6 @@ public interface MappingMetamodel {
Function<NavigablePath,
TableGroup> tableGroupLocator);
MappingModelExpressible<?> lenientlyResolveMappingExpressible(
SqmExpressible<?> sqmExpressible,
Function<NavigablePath, TableGroup> tableGroupLocator);
/**
* Given a Java type, determine the corresponding BindableType to
* use implicitly
@ -67,11 +63,6 @@ public interface MappingMetamodel {
void forEachEntityDescriptor(Consumer<EntityPersister> action);
Stream<EntityPersister> streamEntityDescriptors();
/**
* Given a JPA entity domain type, get the associated Hibernate entity descriptor
*/
EntityPersister resolveEntityDescriptor(EntityDomainType<?> entityDomainType);
/**
* Get an entity mapping descriptor based on its Hibernate entity-name
*

View File

@ -376,11 +376,6 @@ public class MappingMetamodelImpl implements MappingMetamodelImplementor, Metamo
return entityPersisterMap.values().stream();
}
@Override
public EntityPersister resolveEntityDescriptor(EntityDomainType<?> entityDomainType) {
throw new UnsupportedOperationException();
}
@Override
public EntityPersister getEntityDescriptor(String entityName) {
final EntityPersister entityPersister = entityPersisterMap.get( entityName );
@ -750,14 +745,10 @@ public class MappingMetamodelImpl implements MappingMetamodelImplementor, Metamo
}
@Override
public MappingModelExpressible<?> lenientlyResolveMappingExpressible(
public MappingModelExpressible<?> resolveMappingExpressible(
SqmExpressible<?> sqmExpressible,
Function<NavigablePath, TableGroup> tableGroupLocator) {
return resolveMappingExpressible(sqmExpressible, tableGroupLocator );
}
@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();

View File

@ -74,9 +74,7 @@ import jakarta.persistence.criteria.Subquery;
public interface NodeBuilder extends HibernateCriteriaBuilder {
JpaMetamodel getDomainModel();
default TypeConfiguration getTypeConfiguration() {
return getDomainModel().getTypeConfiguration();
}
TypeConfiguration getTypeConfiguration();
boolean isJpaQueryComplianceEnabled();

View File

@ -101,7 +101,7 @@ public abstract class AbstractSqmFunctionDescriptor implements SqmFunctionDescri
ReturnableType<T> impliedResultType,
QueryEngine queryEngine,
TypeConfiguration typeConfiguration) {
argumentsValidator.validate( arguments, getName(), queryEngine);
argumentsValidator.validate( arguments, getName(), typeConfiguration );
return generateSqmFunctionExpression(
arguments,
@ -118,7 +118,7 @@ public abstract class AbstractSqmFunctionDescriptor implements SqmFunctionDescri
ReturnableType<T> impliedResultType,
QueryEngine queryEngine,
TypeConfiguration typeConfiguration) {
argumentsValidator.validate( arguments, getName(), queryEngine );
argumentsValidator.validate( arguments, getName(), typeConfiguration );
return generateSqmAggregateFunctionExpression(
arguments,
@ -137,7 +137,7 @@ public abstract class AbstractSqmFunctionDescriptor implements SqmFunctionDescri
ReturnableType<T> impliedResultType,
QueryEngine queryEngine,
TypeConfiguration typeConfiguration) {
argumentsValidator.validate( arguments, getName(), queryEngine );
argumentsValidator.validate( arguments, getName(), typeConfiguration );
return generateSqmOrderedSetAggregateFunctionExpression(
arguments,
@ -158,7 +158,7 @@ public abstract class AbstractSqmFunctionDescriptor implements SqmFunctionDescri
ReturnableType<T> impliedResultType,
QueryEngine queryEngine,
TypeConfiguration typeConfiguration) {
argumentsValidator.validate( arguments, getName(), queryEngine );
argumentsValidator.validate( arguments, getName(), typeConfiguration );
return generateSqmWindowFunctionExpression(
arguments,
@ -233,7 +233,8 @@ public abstract class AbstractSqmFunctionDescriptor implements SqmFunctionDescri
* Return an SQM node or subtree representing an invocation of this window function
* with the given arguments. This method may be overridden in the case of
* function descriptors that wish to customize creation of the node.
* @param arguments the arguments of the function invocation
*
* @param arguments the arguments of the function invocation
* @param respectNulls
* @param fromFirst
* @param impliedResultType the function return type as inferred from its usage

View File

@ -219,6 +219,11 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
return domainModelAccess.get();
}
@Override
public TypeConfiguration getTypeConfiguration() {
return queryEngine.getTypeConfiguration();
}
@Override
public boolean isJpaQueryComplianceEnabled() {
return jpaComplianceEnabled;

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.query.sqm.produce.function;
import org.hibernate.HibernateException;
import org.hibernate.QueryException;
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.metamodel.mapping.JdbcMapping;
@ -13,6 +14,7 @@ import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.query.sqm.tree.domain.SqmPath;
@ -29,6 +31,7 @@ import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.spi.JdbcTypeRecommendationException;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.spi.TypeConfiguration;
import java.lang.reflect.Type;
import java.sql.Types;
@ -50,19 +53,30 @@ import static org.hibernate.type.SqlTypes.isTemporalType;
/**
* Typechecks the arguments of HQL functions based on the assigned JDBC types.
* The main purpose for doing this is that we want to be able to check named
* queries at startup or build time, and we want to be able to check all queries
* in the IDE.
*
* @apiNote Originally, the main purpose for doing this was that we wanted to be
* able to check named queries at startup or build time, and we wanted
* to be able to check all queries in the IDE. But since Hibernate 6
* it's of more general importance.
*
* @implNote Access to the {@link MappingMetamodel} is very problematic here,
* since we are sometimes called in a context where we have not built
* a {@link org.hibernate.internal.SessionFactoryImpl}, and therefore
* we have no persisters.
*
* @author Gavin King
*/
public class ArgumentTypesValidator implements ArgumentsValidator {
// a JDBC type code of an enum when we don't know if it's mapped STRING or ORDINAL
// this number has to be distinct from every code in SqlTypes!
private static final int ENUM_UNKNOWN_JDBC_TYPE = -101977;
final ArgumentsValidator delegate;
private final FunctionParameterType[] types;
public ArgumentTypesValidator(ArgumentsValidator delegate, FunctionParameterType... types) {
this.types = types;
if (delegate == null ) {
if ( delegate == null ) {
delegate = StandardArgumentsValidators.exactly(types.length);
}
this.delegate = delegate;
@ -78,21 +92,20 @@ public class ArgumentTypesValidator implements ArgumentsValidator {
public void validate(
List<? extends SqmTypedNode<?>> arguments,
String functionName,
MappingMetamodel metamodel) {
delegate.validate( arguments, functionName, metamodel );
TypeConfiguration typeConfiguration) {
delegate.validate( arguments, functionName, typeConfiguration);
int count = 0;
for (SqmTypedNode<?> argument : arguments) {
JdbcTypeIndicators indicators = metamodel.getTypeConfiguration().getCurrentBaseSqlTypeIndicators();
JdbcTypeIndicators indicators = typeConfiguration.getCurrentBaseSqlTypeIndicators();
SqmExpressible<?> nodeType = argument.getNodeType();
FunctionParameterType type = count < types.length ? types[count++] : types[types.length - 1];
if ( nodeType!=null ) {
if ( nodeType != null ) {
JavaType<?> javaType = nodeType.getExpressibleJavaType();
if (javaType != null) {
try {
final JdbcType jdbcType = getJdbcType( metamodel, argument, indicators, javaType );
checkType(
count, functionName, type,
jdbcType.getDefaultSqlTypeCode(),
getJdbcType( typeConfiguration, argument, indicators, javaType ),
javaType.getJavaTypeClass()
);
}
@ -128,43 +141,63 @@ public class ArgumentTypesValidator implements ArgumentsValidator {
}
}
private JdbcType getJdbcType(
MappingMetamodel metamodel,
private int getJdbcType(
TypeConfiguration typeConfiguration,
SqmTypedNode<?> argument,
JdbcTypeIndicators indicators,
JavaType<?> javaType) {
// For enum types, we must try to resolve the JdbcMapping of a possible path
// to be sure we use the correct JdbcType for the validation
final JdbcMapping mapping = javaType.getJavaTypeClass().isEnum()
? getJdbcMapping( argument, metamodel )
: null;
if ( mapping == null ) {
return javaType.getRecommendedJdbcType( indicators );
final JdbcType jdbcType;
if ( javaType.getJavaTypeClass().isEnum() ) {
// we can't tell from the enum class whether it is mapped
// as STRING or ORDINAL, we have to look at the entity
// attribute it belongs to when it occurs as a path expression
final JdbcMapping mapping = getJdbcMappingForEnum( argument, typeConfiguration );
if ( mapping == null ) {
jdbcType = javaType.getRecommendedJdbcType( indicators );
}
else {
// indicates that we don't know if its STRING or ORDINAL
return ENUM_UNKNOWN_JDBC_TYPE;
}
}
else {
return mapping.getJdbcType();
jdbcType = javaType.getRecommendedJdbcType( indicators );
}
return jdbcType.getDefaultSqlTypeCode();
}
private JdbcMapping getJdbcMapping(SqmTypedNode<?> argument, MappingMetamodel metamodel) {
private JdbcMapping getJdbcMappingForEnum(SqmTypedNode<?> argument, TypeConfiguration typeConfiguration) {
if ( argument instanceof SqmPath<?> ) {
final SqmPath<?> path = (SqmPath<?>) argument;
final ModelPartContainer modelPartContainer = getModelPartContainer( path.getLhs(), metamodel );
final ModelPartContainer modelPartContainer = getModelPartContainer( path.getLhs(), typeConfiguration );
final ModelPart part = modelPartContainer.findSubPart( path.getReferencedPathSource().getPathName(), null );
return part.getJdbcMappings().get( 0 );
}
return null;
}
private ModelPartContainer getModelPartContainer(SqmPath<?> path, MappingMetamodel metamodel) {
/**
* @implNote Accesses the given {@link MappingMetamodel}, which is problematic.
*/
private ModelPartContainer getModelPartContainer(SqmPath<?> path, TypeConfiguration typeConfiguration) {
final SqmPath<?> lhs = path.getLhs();
if ( lhs == null ) {
assert path instanceof SqmFrom<?, ?>;
final EntityDomainType<?> entityDomainType = (EntityDomainType<?>) path.getNodeType().getSqmPathType();
return metamodel.getEntityDescriptor( entityDomainType.getHibernateEntityName() );
final MappingMetamodelImplementor mappingMetamodel;
try {
mappingMetamodel = typeConfiguration.getSessionFactory().getMappingMetamodel();
}
catch (HibernateException he) {
// we don't have a SessionFactory, or a MappingMetamodel
return null;
}
return mappingMetamodel.getEntityDescriptor( entityDomainType.getHibernateEntityName() );
}
else {
final ModelPartContainer modelPartContainer = getModelPartContainer( lhs, metamodel );
final ModelPartContainer modelPartContainer = getModelPartContainer( lhs, typeConfiguration );
return (ModelPartContainer) modelPartContainer.findSubPart( path.getReferencedPathSource().getPathName(), null );
}
}
@ -225,7 +258,7 @@ public class ArgumentTypesValidator implements ArgumentsValidator {
}
break;
case STRING:
if ( !isCharacterType(code) ) {
if ( !isCharacterType(code) && code != ENUM_UNKNOWN_JDBC_TYPE ) {
throwError(type, javaType, functionName, count);
}
break;
@ -240,7 +273,7 @@ public class ArgumentTypesValidator implements ArgumentsValidator {
}
break;
case INTEGER:
if ( !isIntegral(code) ) {
if ( !isIntegral(code) && code != ENUM_UNKNOWN_JDBC_TYPE ) {
throwError(type, javaType, functionName, count);
}
break;
@ -291,7 +324,7 @@ public class ArgumentTypesValidator implements ArgumentsValidator {
@Override
public String getSignature() {
String sig = this.delegate.getSignature();
String sig = delegate.getSignature();
for (int i=0; i<types.length; i++) {
String argName = types.length == 1 ? "arg" : "arg" + i;
sig = sig.replace(argName, types[i] + " " + argName);

View File

@ -6,10 +6,10 @@
*/
package org.hibernate.query.sqm.produce.function;
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.query.spi.QueryEngine;
import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.type.spi.TypeConfiguration;
import java.util.List;
@ -25,17 +25,17 @@ public interface ArgumentsValidator {
/**
* Perform validation that may be done using the {@link SqmTypedNode} tree and assigned Java types.
*
* @deprecated Use {@link #validate(List, String, MappingMetamodel)}
* @deprecated Use {@link #validate(List, String, TypeConfiguration)}
*/
@Deprecated(since = "6.2")
default void validate(List<? extends SqmTypedNode<?>> arguments, String functionName, QueryEngine queryEngine) {
validate( arguments, functionName, queryEngine.getTypeConfiguration().getSessionFactory().getMappingMetamodel() );
validate( arguments, functionName, queryEngine.getTypeConfiguration() );
}
/**
* Perform validation that may be done using the {@link SqmTypedNode} tree and assigned Java types.
*/
default void validate(List<? extends SqmTypedNode<?>> arguments, String functionName, MappingMetamodel metamodel) {}
default void validate(List<? extends SqmTypedNode<?>> arguments, String functionName, TypeConfiguration typeConfiguration) {}
/**
* Pretty-print the signature of the argument list.

View File

@ -7,8 +7,8 @@
package org.hibernate.query.sqm.produce.function;
import org.hibernate.QueryException;
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.type.spi.TypeConfiguration;
import java.util.List;
import java.util.Locale;
@ -43,7 +43,7 @@ public final class StandardArgumentsValidators {
public void validate(
List<? extends SqmTypedNode<?>> arguments,
String functionName,
MappingMetamodel metamodel) {
TypeConfiguration typeConfiguration) {
if ( !arguments.isEmpty() ) {
throw new QueryException(
String.format(
@ -71,7 +71,7 @@ public final class StandardArgumentsValidators {
public void validate(
List<? extends SqmTypedNode<?>> arguments,
String functionName,
MappingMetamodel metamodel) {
TypeConfiguration typeConfiguration) {
if ( arguments.size() < minNumOfArgs ) {
throw new QueryException(
String.format(
@ -108,7 +108,7 @@ public final class StandardArgumentsValidators {
public void validate(
List<? extends SqmTypedNode<?>> arguments,
String functionName,
MappingMetamodel metamodel) {
TypeConfiguration typeConfiguration) {
if ( arguments.size() != number ) {
throw new QueryException(
String.format(
@ -147,7 +147,7 @@ public final class StandardArgumentsValidators {
public void validate(
List<? extends SqmTypedNode<?>> arguments,
String functionName,
MappingMetamodel metamodel) {
TypeConfiguration typeConfiguration) {
if ( arguments.size() > maxNumOfArgs ) {
throw new QueryException(
String.format(
@ -182,7 +182,7 @@ public final class StandardArgumentsValidators {
public void validate(
List<? extends SqmTypedNode<?>> arguments,
String functionName,
MappingMetamodel metamodel) {
TypeConfiguration typeConfiguration) {
if (arguments.size() < minNumOfArgs || arguments.size() > maxNumOfArgs) {
throw new QueryException(
String.format(
@ -221,7 +221,7 @@ public final class StandardArgumentsValidators {
public void validate(
List<? extends SqmTypedNode<?>> arguments,
String functionName,
MappingMetamodel metamodel) {
TypeConfiguration typeConfiguration) {
for ( SqmTypedNode<?> argument : arguments ) {
Class<?> argType = argument.getNodeJavaType().getJavaTypeClass();
if ( !javaType.isAssignableFrom( argType ) ) {
@ -250,11 +250,11 @@ public final class StandardArgumentsValidators {
public void validate(
List<? extends SqmTypedNode<?>> arguments,
String functionName,
MappingMetamodel metamodel) {
TypeConfiguration typeConfiguration) {
validators.forEach( individualValidator -> individualValidator.validate(
arguments,
functionName,
metamodel
typeConfiguration
) );
}
};