finally sort out handling of @Id and @Version in query validator

also remove 'this' hacks made obsolete by Steve's work on core
This commit is contained in:
Gavin King 2024-09-14 11:07:20 +02:00
parent d0295473b2
commit e9223cb984
14 changed files with 287 additions and 60 deletions

View File

@ -420,11 +420,10 @@ public abstract class AbstractIdentifiableType<J>
}
else {
assert type instanceof EmbeddableDomainType;
final EmbeddableDomainType<?> compositeType = (EmbeddableDomainType<?>) type;
return new EmbeddedSqmPathSource<>(
EntityIdentifierMapping.ID_ROLE_NAME,
(SqmPathSource) id,
compositeType,
(EmbeddableDomainType<?>) type,
Bindable.BindableType.SINGULAR_ATTRIBUTE,
id.isGeneric()
);

View File

@ -2971,9 +2971,15 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
final SqmPath<?> sqmPath = consumeDomainPath( ctx.path() );
final DomainType<?> sqmPathType = sqmPath.getReferencedPathSource().getSqmPathType();
if ( sqmPathType instanceof IdentifiableDomainType<?> ) {
final SqmPathSource<?> identifierDescriptor = ( (IdentifiableDomainType<?>) sqmPathType ).getIdentifierDescriptor();
final IdentifiableDomainType<?> identifiableType = (IdentifiableDomainType<?>) sqmPathType;
final SqmPathSource<?> identifierDescriptor = identifiableType.getIdentifierDescriptor();
if ( identifierDescriptor == null ) {
// mainly for benefit of Hibernate Processor
throw new FunctionArgumentException( "Argument '" + sqmPath.getNavigablePath()
+ "' of 'id()' is a '" + identifiableType.getTypeName()
+ "' and does not have a well-defined '@Id' attribute" );
}
return sqmPath.get( identifierDescriptor.getPathName() );
}
else if ( sqmPath instanceof SqmAnyValuedSimplePath<?> ) {
@ -2981,7 +2987,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
}
else {
throw new FunctionArgumentException( "Argument '" + sqmPath.getNavigablePath()
+ "' of 'id()' function does not resolve to an entity type" );
+ "' of 'id()' does not resolve to an entity type" );
}
}
@ -2994,26 +3000,22 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
public SqmPath<?> visitEntityVersionReference(HqlParser.EntityVersionReferenceContext ctx) {
final SqmPath<?> sqmPath = consumeDomainPath( ctx.path() );
final DomainType<?> sqmPathType = sqmPath.getReferencedPathSource().getSqmPathType();
if ( sqmPathType instanceof IdentifiableDomainType<?> ) {
@SuppressWarnings("unchecked")
final IdentifiableDomainType<Object> identifiableType = (IdentifiableDomainType<Object>) sqmPathType;
final SingularPersistentAttribute<Object, ?> versionAttribute = identifiableType.findVersionAttribute();
if ( versionAttribute == null ) {
throw new FunctionArgumentException(
String.format(
"Argument '%s' of 'version()' function resolved to entity type '%s' which does not have a '@Version' attribute",
sqmPath.getNavigablePath(),
identifiableType.getTypeName()
)
);
final IdentifiableDomainType<?> identifiableType = (IdentifiableDomainType<?>) sqmPathType;
if ( !identifiableType.hasVersionAttribute() ) {
throw new FunctionArgumentException( "Argument '" + sqmPath.getNavigablePath()
+ "' of 'version()' is a '" + identifiableType.getTypeName()
+ "' and does not have a '@Version' attribute" );
}
@SuppressWarnings("unchecked")
final SingularPersistentAttribute<Object, ?> versionAttribute =
(SingularPersistentAttribute<Object, ?>) identifiableType.findVersionAttribute();
return sqmPath.get( versionAttribute );
}
else {
throw new FunctionArgumentException( "Argument '" + sqmPath.getNavigablePath()
+ "' of 'version()' function does not resolve to an entity type" );
+ "' of 'version()' does not resolve to an entity type" );
}
}
@Override
@ -3032,35 +3034,29 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
if ( sqmPathType instanceof IdentifiableDomainType<?> ) {
@SuppressWarnings("unchecked")
final IdentifiableDomainType<Object> identifiableType = (IdentifiableDomainType<? super Object>) sqmPathType;
final IdentifiableDomainType<Object> identifiableType = (IdentifiableDomainType<Object>) sqmPathType;
final List<? extends PersistentAttribute<Object, ?>> attributes = identifiableType.findNaturalIdAttributes();
if ( attributes == null ) {
throw new FunctionArgumentException(
String.format(
"Argument '%s' of 'naturalid()' function resolved to entity type '%s' which does not have a natural id",
sqmPath.getNavigablePath(),
identifiableType.getTypeName()
)
throw new FunctionArgumentException( "Argument '" + sqmPath.getNavigablePath()
+ "' of 'naturalid()' is a '" + identifiableType.getTypeName()
+ "' and does not have a natural id"
);
}
else if ( attributes.size() >1 ) {
throw new FunctionArgumentException(
String.format(
"Argument '%s' of 'naturalid()' function resolved to entity type '%s' which has a composite natural id",
sqmPath.getNavigablePath(),
identifiableType.getTypeName()
)
throw new FunctionArgumentException( "Argument '" + sqmPath.getNavigablePath()
+ "' of 'naturalid()' is a '" + identifiableType.getTypeName()
+ "' and has a composite natural id"
);
}
@SuppressWarnings("unchecked")
SingularAttribute<Object, ?> naturalIdAttribute
final SingularAttribute<Object, ?> naturalIdAttribute
= (SingularAttribute<Object, ?>) attributes.get(0);
return sqmPath.get( naturalIdAttribute );
}
throw new FunctionArgumentException( "Argument '" + sqmPath.getNavigablePath()
+ "' of 'naturalid()' function does not resolve to an entity type" );
+ "' of 'naturalid()' does not resolve to an entity type" );
}
//
// @Override

View File

@ -3,5 +3,5 @@ package org.hibernate.processor.test.data.basic;
import jakarta.data.repository.Repository;
@Repository
public interface Concrete extends IdOperations<Book> {
public interface Concrete extends IdOperations<Thing> {
}

View File

@ -19,7 +19,7 @@ import static org.hibernate.processor.test.util.TestUtil.getMetaModelSourceAsStr
*/
public class DataTest extends CompilationTest {
@Test
@WithClasses({ Author.class, Book.class, BookAuthorRepository.class, IdOperations.class, Concrete.class })
@WithClasses({ Author.class, Book.class, BookAuthorRepository.class, IdOperations.class, Concrete.class, Thing.class })
public void test() {
System.out.println( getMetaModelSourceAsString( Author.class ) );
System.out.println( getMetaModelSourceAsString( Book.class ) );

View File

@ -23,11 +23,6 @@ import jakarta.data.Order;
import jakarta.data.Sort;
import jakarta.data.repository.Query;
/**
* This interface contains common operations for the NaturalNumbers and AsciiCharacters repositories.
*
* @param <T> type of entity.
*/
public interface IdOperations<T> {
@Query("where id(this) between ?1 and ?2")
Stream<T> findByIdBetween(long minimum, long maximum, Sort<T> sort);

View File

@ -0,0 +1,9 @@
package org.hibernate.processor.test.data.basic;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
@Entity
public class Thing {
@Id long id;
}

View File

@ -0,0 +1,7 @@
package org.hibernate.processor.test.data.versioned;
import jakarta.persistence.Entity;
@Entity
public class SpecialVersioned extends Versioned {
}

View File

@ -0,0 +1,17 @@
package org.hibernate.processor.test.data.versioned;
import jakarta.data.repository.Query;
import jakarta.data.repository.Repository;
@Repository
public interface SpecialVersionedRepo {
@Query("where id(this) = ?1")
SpecialVersioned forId(long id);
@Query("where id(this) = ?1 and version(this) = ?2")
SpecialVersioned forIdAndVersion(long id, int version);
@Query("select count(this) from SpecialVersioned")
long count();
}

View File

@ -0,0 +1,11 @@
package org.hibernate.processor.test.data.versioned;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Version;
@Entity
public class Versioned {
@Id long id;
@Version int version;
}

View File

@ -0,0 +1,17 @@
package org.hibernate.processor.test.data.versioned;
import jakarta.data.repository.Query;
import jakarta.data.repository.Repository;
@Repository
public interface VersionedRepo {
@Query("where id(this) = ?1")
Versioned forId(long id);
@Query("where id(this) = ?1 and version(this) = ?2")
Versioned forIdAndVersion(long id, int version);
@Query("select count(this) from Versioned")
long count();
}

View File

@ -0,0 +1,31 @@
/*
* 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.processor.test.data.versioned;
import org.hibernate.processor.test.util.CompilationTest;
import org.hibernate.processor.test.util.WithClasses;
import org.junit.Test;
import static org.hibernate.processor.test.util.TestUtil.assertMetamodelClassGeneratedFor;
import static org.hibernate.processor.test.util.TestUtil.getMetaModelSourceAsString;
/**
* @author Gavin King
*/
public class VersionedTest extends CompilationTest {
@Test
@WithClasses({ Versioned.class, VersionedRepo.class, SpecialVersioned.class, SpecialVersionedRepo.class })
public void test() {
System.out.println( getMetaModelSourceAsString( VersionedRepo.class ) );
assertMetamodelClassGeneratedFor( Versioned.class, true );
assertMetamodelClassGeneratedFor( Versioned.class );
assertMetamodelClassGeneratedFor( SpecialVersioned.class, true );
assertMetamodelClassGeneratedFor( SpecialVersioned.class );
assertMetamodelClassGeneratedFor( VersionedRepo.class );
assertMetamodelClassGeneratedFor( SpecialVersionedRepo.class );
}
}

View File

@ -13,6 +13,7 @@ import org.hibernate.persister.entity.DiscriminatorMetadata;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Queryable;
import org.hibernate.tuple.entity.EntityMetamodel;
import org.hibernate.type.BasicType;
import org.hibernate.type.Type;
import java.io.Serializable;
@ -115,18 +116,36 @@ public abstract class MockEntityPersister implements EntityPersister, Queryable,
abstract Type createPropertyType(String propertyPath);
@Override
public Type getIdentifierType() {
//TODO: propertyType(getIdentifierPropertyName())
return factory.getTypeConfiguration().getBasicTypeForJavaType(Long.class);
}
/**
* Override on subclasses!
*/
@Override
public String getIdentifierPropertyName() {
//TODO: return the correct @Id property name
return "id";
return getRootEntityPersister().identifierPropertyName();
}
protected abstract String identifierPropertyName();
/**
* Override on subclasses!
*/
@Override
public Type getIdentifierType() {
return getRootEntityPersister().identifierType();
}
protected abstract Type identifierType();
/**
* Override on subclasses!
*/
@Override
public BasicType<?> getVersionType() {
return getRootEntityPersister().versionType();
}
protected abstract BasicType<?> versionType();
@Override
public Type toType(String propertyName) throws QueryException {
Type type = getPropertyType(propertyName);
@ -139,13 +158,10 @@ public abstract class MockEntityPersister implements EntityPersister, Queryable,
}
@Override
public String getRootEntityName() {
for (MockEntityPersister persister : factory.getMockEntityPersisters()) {
if (this != persister && persister.isSubclassPersister(this)) {
return persister.getRootEntityName();
}
}
return entityName;
public abstract String getRootEntityName();
public MockEntityPersister getRootEntityPersister() {
return factory.createMockEntityPersister(getRootEntityName());
}
@Override
@ -217,7 +233,12 @@ public abstract class MockEntityPersister implements EntityPersister, Queryable,
@Override
public int getVersionProperty() {
return -66;
return 0;
}
@Override
public boolean isVersioned() {
return true;
}
@Override

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.processor.validation;
import jakarta.persistence.metamodel.Bindable;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.CustomEntityDirtinessStrategy;
import org.hibernate.EntityNameResolver;
@ -57,16 +58,22 @@ import org.hibernate.metamodel.internal.JpaMetaModelPopulationSetting;
import org.hibernate.metamodel.internal.JpaStaticMetaModelPopulationSetting;
import org.hibernate.metamodel.internal.MetadataContext;
import org.hibernate.metamodel.internal.RuntimeMetamodelsImpl;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.model.domain.BasicDomainType;
import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.metamodel.model.domain.EmbeddableDomainType;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.metamodel.model.domain.ManagedDomainType;
import org.hibernate.metamodel.model.domain.PersistentAttribute;
import org.hibernate.metamodel.model.domain.SingularPersistentAttribute;
import org.hibernate.metamodel.model.domain.internal.AbstractAttribute;
import org.hibernate.metamodel.model.domain.internal.AbstractPluralAttribute;
import org.hibernate.metamodel.model.domain.internal.BagAttributeImpl;
import org.hibernate.metamodel.model.domain.internal.BasicSqmPathSource;
import org.hibernate.metamodel.model.domain.internal.BasicTypeImpl;
import org.hibernate.metamodel.model.domain.internal.EmbeddableTypeImpl;
import org.hibernate.metamodel.model.domain.internal.EmbeddedSqmPathSource;
import org.hibernate.metamodel.model.domain.internal.EntityTypeImpl;
import org.hibernate.metamodel.model.domain.internal.JpaMetamodelImpl;
import org.hibernate.metamodel.model.domain.internal.ListAttributeImpl;
@ -102,6 +109,7 @@ import org.hibernate.query.sqm.sql.StandardSqmTranslatorFactory;
import org.hibernate.stat.internal.StatisticsImpl;
import org.hibernate.stat.spi.StatisticsImplementor;
import org.hibernate.type.BagType;
import org.hibernate.type.BasicType;
import org.hibernate.type.CollectionType;
import org.hibernate.type.CompositeType;
import org.hibernate.type.ListType;
@ -336,6 +344,12 @@ public abstract class MockSessionFactory
.getIdentifierType();
}
public BasicType<?> getVersionType(String className)
throws MappingException {
return createEntityPersister(className)
.getVersionType();
}
@Override
public String getIdentifierPropertyName(String className)
throws MappingException {
@ -915,9 +929,73 @@ public abstract class MockSessionFactory
metamodel.getJpaMetamodel());
}
@Override
public SingularPersistentAttribute<? super X, ?> findVersionAttribute() {
final BasicType<?> type = getVersionType(getHibernateEntityName());
if (type == null) {
return null;
}
else {
return new SingularAttributeImpl<>(
MockEntityDomainType.this,
"{version}",
AttributeClassification.BASIC,
type,
type.getRelationalJavaType(),
null,
false,
true,
false,
false,
metadataContext
);
}
}
@Override
public boolean hasVersionAttribute() {
return getVersionType(getHibernateEntityName()) != null;
}
@Override
public SqmPathSource<?> getIdentifierDescriptor() {
final Type type = getIdentifierType(getHibernateEntityName());
if (type == null) {
return null;
}
else if (type instanceof BasicDomainType) {
return new BasicSqmPathSource<>(
EntityIdentifierMapping.ID_ROLE_NAME,
null,
(BasicDomainType<?>) type,
MockEntityDomainType.this.getExpressibleJavaType(),
Bindable.BindableType.SINGULAR_ATTRIBUTE,
false
);
}
else if (type instanceof EmbeddableDomainType) {
return new EmbeddedSqmPathSource<>(
EntityIdentifierMapping.ID_ROLE_NAME,
null,
(EmbeddableDomainType<?>) type,
Bindable.BindableType.SINGULAR_ATTRIBUTE,
false
);
}
else {
return null;
}
}
@Override
public SqmPathSource<?> findSubPathSource(String name, JpaMetamodelImplementor metamodel) {
SqmPathSource<?> source = super.findSubPathSource(name, metamodel);
switch (name) {
case EntityIdentifierMapping.ID_ROLE_NAME:
return getIdentifierDescriptor();
case "{version}":
return findVersionAttribute();
}
final SqmPathSource<?> source = super.findSubPathSource(name, metamodel);
if ( source != null ) {
return source;
}

View File

@ -10,6 +10,7 @@ import jakarta.persistence.AccessType;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.PropertyNotFoundException;
import org.hibernate.engine.spi.Mapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.type.BasicType;
import org.hibernate.type.CollectionType;
import org.hibernate.type.CompositeType;
@ -148,6 +149,7 @@ public abstract class ProcessorSessionFactory extends MockSessionFactory {
}
}
@Override
Type propertyType(String typeName, String propertyPath) {
TypeElement type = findClassByQualifiedName(typeName);
@ -370,6 +372,21 @@ public abstract class ProcessorSessionFactory extends MockSessionFactory {
initSubclassPersisters();
}
@Override
public String getRootEntityName() {
TypeElement result = type;
TypeMirror superclass = type.getSuperclass();
while ( superclass!=null && superclass.getKind() == TypeKind.DECLARED ) {
final DeclaredType declaredType = (DeclaredType) superclass;
final TypeElement typeElement = (TypeElement) declaredType.asElement();
if ( hasAnnotation(typeElement, "Entity") ) {
result = typeElement;
}
superclass = typeElement.getSuperclass();
}
return ProcessorSessionFactory.getEntityName(result);
}
@Override
boolean isSubclassPersister(MockEntityPersister entityPersister) {
EntityPersister persister = (EntityPersister) entityPersister;
@ -383,6 +400,35 @@ public abstract class ProcessorSessionFactory extends MockSessionFactory {
factory.propertyType(symbol, getEntityName(), propertyPath, defaultAccessType);
}
@Override
public String identifierPropertyName() {
for (Element element : type.getEnclosedElements()) {
if ( hasAnnotation(element, "Id") || hasAnnotation(element, "EmbeddedId") ) {
return element.getSimpleName().toString();
}
}
return "id";
}
@Override
public Type identifierType() {
for (Element element : type.getEnclosedElements()) {
if ( hasAnnotation(element, "Id")|| hasAnnotation(element, "EmbeddedId") ) {
return factory.propertyType(element, getEntityName(), EntityIdentifierMapping.ID_ROLE_NAME, defaultAccessType);
}
}
return null;
}
@Override
public BasicType<?> versionType() {
for (Element element : type.getEnclosedElements()) {
if ( hasAnnotation(element, "Version") ) {
return (BasicType<?>) factory.propertyType(element, getEntityName(), "{version}", defaultAccessType);
}
}
return null;
}
}
public abstract static class ToManyAssociationPersister extends MockCollectionPersister {