From b23e5ba54a68f894faf08a955487bc7b620e2529 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Tue, 14 Jan 2020 07:24:07 -0600 Subject: [PATCH] support for canonical reference to id and version. consider other entity parts - natural-id, tenant-discriminator, etc --- .../org/hibernate/grammars/hql/HqlLexer.g4 | 4 ++ .../org/hibernate/grammars/hql/HqlParser.g4 | 18 +++++ .../hql/internal/SemanticQueryBuilder.java | 67 ++++++++++++++++++- .../test/query/hql/AttributePathTests.java | 16 +++-- 4 files changed, 97 insertions(+), 8 deletions(-) diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 index df6a330ad3..6e478615aa 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 @@ -141,6 +141,10 @@ ARROW : '->'; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Keywords +ID : [iI][dD]; +VERSION : [vV] [eE] [rR] [sS] [iI] [oO] [nN]; +NATURALID : [nN] [aA] [tT] [uU] [rR] [aA] [lL] [iI] [dD]; + ABS : [aA] [bB] [sS]; ALL : [aA] [lL] [lL]; AND : [aA] [nN] [dD]; diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 index a8ed67777a..9afd557e4c 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 @@ -413,6 +413,9 @@ expression | literal # LiteralExpression | parameter # ParameterExpression | entityTypeReference # EntityTypeExpression + | entityIdReference # EntityIdExpression + | entityVersionReference # EntityVersionExpression + | entityNaturalIdReference # EntityNaturalIdExpression | path # PathExpression | function # FunctionExpression | LEFT_PAREN subQuery RIGHT_PAREN # SubQueryExpression @@ -422,6 +425,18 @@ entityTypeReference : TYPE LEFT_PAREN (path | parameter) RIGHT_PAREN ; +entityIdReference + : ID LEFT_PAREN path RIGHT_PAREN pathContinuation? + ; + +entityVersionReference + : VERSION LEFT_PAREN path RIGHT_PAREN + ; + +entityNaturalIdReference + : NATURALID LEFT_PAREN path RIGHT_PAREN pathContinuation? + ; + caseStatement : simpleCaseStatement | searchedCaseStatement @@ -987,6 +1002,7 @@ identifier | GREATEST | GROUP | HOUR + | ID | IFNULL | IN | INDEX @@ -1020,6 +1036,7 @@ identifier | MOD | MONTH | NANOSECOND + | NATURALID | NEW | NOT | NULLIF @@ -1056,6 +1073,7 @@ identifier | UPDATE | UPPER | VALUE + | VERSION | WEEK | WHERE | WITH diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java index a00c2a7bcb..5aad939c1a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java @@ -39,11 +39,13 @@ import org.hibernate.metamodel.mapping.MappingModelExpressable; import org.hibernate.metamodel.model.domain.AllowableFunctionReturnType; import org.hibernate.metamodel.model.domain.BasicDomainType; +import org.hibernate.metamodel.model.domain.DomainType; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.IdentifiableDomainType; import org.hibernate.metamodel.model.domain.ManagedDomainType; import org.hibernate.metamodel.model.domain.MapPersistentAttribute; import org.hibernate.metamodel.model.domain.PluralPersistentAttribute; +import org.hibernate.metamodel.model.domain.SingularPersistentAttribute; import org.hibernate.query.BinaryArithmeticOperator; import org.hibernate.query.ComparisonOperator; import org.hibernate.query.PathException; @@ -1320,6 +1322,70 @@ else if ( ctx.entityTypeReference().path() != null ) { throw new ParsingException( "Could not interpret grammar context as 'entity type' expression : " + ctx.getText() ); } + @Override + public SqmPath visitEntityIdExpression(HqlParser.EntityIdExpressionContext ctx) { + return visitEntityIdReference( ctx.entityIdReference() ); + } + + @Override + public SqmPath visitEntityIdReference(HqlParser.EntityIdReferenceContext ctx) { + final SqmPath sqmPath = consumeDomainPath( ctx.path() ); + final DomainType sqmPathType = sqmPath.getReferencedPathSource().getSqmPathType(); + + if ( sqmPathType instanceof IdentifiableDomainType ) { + //noinspection unchecked + final SqmPath idPath = ( (IdentifiableDomainType) sqmPathType ).getIdentifierDescriptor().createSqmPath( + sqmPath, + this + ); + + if ( ctx.pathContinuation() == null ) { + return idPath; + } + + throw new NotYetImplementedFor6Exception( "Path continuation from `id()` reference not yet implemented" ); + } + + throw new SemanticException( "Path does not reference an identifiable-type : " + sqmPath.getNavigablePath().getFullPath() ); + } + + @Override + public SqmPath visitEntityVersionExpression(HqlParser.EntityVersionExpressionContext ctx) { + return visitEntityVersionReference( ctx.entityVersionReference() ); + } + + @Override + public SqmPath visitEntityVersionReference(HqlParser.EntityVersionReferenceContext ctx) { + final SqmPath sqmPath = consumeDomainPath( ctx.path() ); + final DomainType sqmPathType = sqmPath.getReferencedPathSource().getSqmPathType(); + + if ( sqmPathType instanceof IdentifiableDomainType ) { + final IdentifiableDomainType identifiableType = (IdentifiableDomainType) sqmPathType; + final SingularPersistentAttribute versionAttribute = identifiableType.findVersionAttribute(); + if ( versionAttribute == null ) { + throw new SemanticException( + "`" + sqmPath.getNavigablePath().getFullPath() + "` resolved to an identifiable-type (`" + + identifiableType.getTypeName() + "`) which does not define a version" + ); + } + + //noinspection unchecked + return versionAttribute.createSqmPath( sqmPath, this ); + } + + throw new SemanticException( "Path does not reference an identifiable-type : " + sqmPath.getNavigablePath().getFullPath() ); + } + + @Override + public SqmPath visitEntityNaturalIdExpression(HqlParser.EntityNaturalIdExpressionContext ctx) { + return visitEntityNaturalIdReference( ctx.entityNaturalIdReference() ); + } + + @Override + public SqmPath visitEntityNaturalIdReference(HqlParser.EntityNaturalIdReferenceContext ctx) { + throw new NotYetImplementedFor6Exception( "Support for HQL natural-id references not yet implemented" ); + } + @Override @SuppressWarnings("unchecked") public SqmMapEntryReference visitMapEntrySelection(HqlParser.MapEntrySelectionContext ctx) { @@ -3551,7 +3617,6 @@ public SqmPath visitMapKeyNavigablePath(HqlParser.MapKeyNavigablePathContext ctx return result; } - private SqmPath consumeDomainPath(HqlParser.PathContext parserPath) { final SemanticPathPart consumedPart = (SemanticPathPart) parserPath.accept( this ); if ( consumedPart instanceof SqmPath ) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/AttributePathTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/AttributePathTests.java index 6181790fb6..b303853667 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/AttributePathTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/AttributePathTests.java @@ -7,18 +7,16 @@ package org.hibernate.orm.test.query.hql; import java.util.List; - import javax.persistence.Entity; import javax.persistence.Id; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.SingularPersistentAttribute; -import org.hibernate.metamodel.model.domain.internal.SingularAttributeImpl; import org.hibernate.orm.test.query.sqm.BaseSqmUnitTest; import org.hibernate.orm.test.query.sqm.domain.Person; import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath; -import org.hibernate.query.sqm.tree.domain.SqmSimplePath; import org.hibernate.query.sqm.tree.domain.SqmPath; +import org.hibernate.query.sqm.tree.domain.SqmSimplePath; import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.from.SqmRoot; import org.hibernate.query.sqm.tree.predicate.SqmComparisonPredicate; @@ -29,8 +27,6 @@ import org.junit.jupiter.api.Test; -import org.hamcrest.CoreMatchers; - import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; @@ -112,8 +108,6 @@ public void testEntityIdReferences() { interpretSelect( "select s.mate from Person s where s.id = ?1" ); interpretSelect( "select s.mate from Person s where s.pk = ?1" ); - // NOTE: this next form does not (yet) work... - interpretSelect( "select s.mate from Person s where s.{id} = ?1" ); final EntityDomainType entity = sessionFactory().getRuntimeMetamodels() .getJpaMetamodel() @@ -131,6 +125,14 @@ public void testEntityIdReferences() { assertThat( ( (SqmPath) pkRef ).getJavaType(), sameInstance( String.class ) ); } + @Test + public void testCanonicalReferences() { + final SqmSelectStatement sqm = interpretSelect( "select s.mate from Person s where id(s) = ?1" ); + assertThat( sqm.getQuerySpec().getRestriction(), notNullValue() ); + final SqmComparisonPredicate restriction = (SqmComparisonPredicate) sqm.getQuerySpec().getRestriction(); + assertThat( restriction, notNullValue() ); + } + @Test public void testManyToOneReference() { final SqmSelectStatement sqm = interpretSelect( "select s.mate from Person s" );