From 1006d3b19144abfb9b245f42c820a2b0b3b0d242 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Thu, 22 Apr 2021 16:10:05 +0200 Subject: [PATCH] [#1294] Fix various issues with type resolving paths that match entity names --- CHANGELOG.md | 1 + .../persistence/impl/JoinManager.java | 12 ++-- .../expression/AbstractExpressionFactory.java | 8 +++ .../testsuite/entity/DocumentHolder.java | 62 ++++++++++++++++ .../main/resources/META-INF/persistence.xml | 1 + .../MetamodelBuildingContextImpl.java | 11 +++ .../basic/AttributeMatchesEntityNameTest.java | 70 +++++++++++++++++++ 7 files changed, 158 insertions(+), 7 deletions(-) create mode 100644 core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/DocumentHolder.java create mode 100644 entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/basic/AttributeMatchesEntityNameTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 216d249dce..c4873b2b10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ None yet * Workaround Hibernate proxy field access bug for non-pk relations * Fix issues with de-serializing of singular entity view attributes that use a collection type * Add Cockroach DBMS detection and a `DbmsDialect` implementation +* Fix various issues with type resolving paths that match entity names ### Backwards-incompatible changes diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/JoinManager.java b/core/impl/src/main/java/com/blazebit/persistence/impl/JoinManager.java index 89c7a696a2..307b82c6c3 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/JoinManager.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/JoinManager.java @@ -2866,7 +2866,7 @@ public void implicitJoin(Expression expression, boolean joinAllowed, boolean sin Type type = attributeHolder.getAttributeType(); result = new JoinResult(currentResult.baseNode, Arrays.asList(elementString), type, -1, -1); } else if (metamodel.getManagedType(ExtendedManagedType.class, current.getManagedType()).getAttributes().get(associationName) != null) { - Expression resultExpr = expressionFactory.createSimpleExpression(associationName, false); + Expression resultExpr = new PathExpression(new PropertyExpression(associationName)); AttributeHolder attributeHolder = JpaUtils.getAttributeForJoining( metamodel, current.getNodeType(), @@ -3475,13 +3475,11 @@ private JoinResult implicitJoin(JoinNode current, List resultFields, Pat if (resultFields.isEmpty()) { return new JoinResult(current, null, current == null ? null : current.getNodeType(), singleValuedAssociationNameStartIndex, singleValuedAssociationNameEndIndex); } else { - StringBuilder sb = new StringBuilder(); - sb.append(resultFields.get(0)); - for (int i = 1; i < resultFields.size(); i++) { - sb.append('.'); - sb.append(resultFields.get(i)); + List pathElementExpressions = new ArrayList<>(resultFields.size()); + for (int i = 0; i < resultFields.size(); i++) { + pathElementExpressions.add(new PropertyExpression(resultFields.get(i))); } - Expression expression = expressionFactory.createSimpleExpression(sb.toString(), false); + Expression expression = new PathExpression(pathElementExpressions); Type type = JpaUtils.getAttributeForJoining(metamodel, current.getNodeType(), expression, null).getAttributeType(); return new JoinResult(current, resultFields, type, singleValuedAssociationNameStartIndex, singleValuedAssociationNameEndIndex); } diff --git a/core/parser/src/main/java/com/blazebit/persistence/parser/expression/AbstractExpressionFactory.java b/core/parser/src/main/java/com/blazebit/persistence/parser/expression/AbstractExpressionFactory.java index 9e901df1db..b7d541d77e 100644 --- a/core/parser/src/main/java/com/blazebit/persistence/parser/expression/AbstractExpressionFactory.java +++ b/core/parser/src/main/java/com/blazebit/persistence/parser/expression/AbstractExpressionFactory.java @@ -206,6 +206,14 @@ public Expression createPathExpression(String expression, MacroConfiguration mac List pathElements = new ArrayList<>(1); pathElements.add((PathElementExpression) expr); return new PathExpression(pathElements); + } else if (expr instanceof EntityLiteral) { + // Special case where the path matches an entity name + String[] parts = ((EntityLiteral) expr).getOriginalExpression().split("\\."); + List pathElements = new ArrayList<>(parts.length); + for (String part : parts) { + pathElements.add(new PropertyExpression(part)); + } + return new PathExpression(pathElements); } else if (expr instanceof FunctionExpression) { return expr; } else { diff --git a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/DocumentHolder.java b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/DocumentHolder.java new file mode 100644 index 0000000000..d15f2c6b90 --- /dev/null +++ b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/DocumentHolder.java @@ -0,0 +1,62 @@ +/* + * Copyright 2014 - 2021 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.testsuite.entity; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; +import java.io.Serializable; + +/** + * + * @author Christian Beikov + * @since 1.6.0 + */ +@Entity +public class DocumentHolder implements Serializable { + private static final long serialVersionUID = 1L; + + @Id + private Long id; + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "document_id") + // CHECKSTYLE:OFF: MemberName + private Document Document; + // CHECKSTYLE:ON: MemberName + + public DocumentHolder() { + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Document getDocument() { + return Document; + } + + public void setDocument(Document document) { + this.Document = document; + } + +} diff --git a/core/testsuite/src/main/resources/META-INF/persistence.xml b/core/testsuite/src/main/resources/META-INF/persistence.xml index 1dcf92c90e..9e8e13662b 100644 --- a/core/testsuite/src/main/resources/META-INF/persistence.xml +++ b/core/testsuite/src/main/resources/META-INF/persistence.xml @@ -25,6 +25,7 @@ com.blazebit.persistence.testsuite.entity.DocumentForOneToOneJoinTable com.blazebit.persistence.testsuite.entity.DocumentForOneToOne com.blazebit.persistence.testsuite.entity.DocumentForSimpleOneToOne + com.blazebit.persistence.testsuite.entity.DocumentHolder com.blazebit.persistence.testsuite.entity.DocumentInfo com.blazebit.persistence.testsuite.entity.DocumentInfoSimple com.blazebit.persistence.testsuite.entity.DocumentNodeCTE diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MetamodelBuildingContextImpl.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MetamodelBuildingContextImpl.java index f89be59366..a4fee4ca32 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MetamodelBuildingContextImpl.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MetamodelBuildingContextImpl.java @@ -19,6 +19,7 @@ import com.blazebit.persistence.parser.AliasReplacementVisitor; import com.blazebit.persistence.parser.EntityMetamodel; import com.blazebit.persistence.parser.expression.AbstractCachingExpressionFactory; +import com.blazebit.persistence.parser.expression.EntityLiteral; import com.blazebit.persistence.parser.expression.Expression; import com.blazebit.persistence.parser.expression.ExpressionFactory; import com.blazebit.persistence.parser.expression.MacroConfiguration; @@ -54,6 +55,7 @@ import com.blazebit.persistence.view.spi.type.TypeConverter; import com.blazebit.reflection.ReflectionUtils; +import javax.persistence.metamodel.Attribute; import javax.persistence.metamodel.ManagedType; import java.lang.annotation.Annotation; import java.util.ArrayList; @@ -346,6 +348,15 @@ public List getPossibleTarget )); } Expression simpleExpression = typeValidationExpressionFactory.createSimpleExpression(expression, false, false, true); + if (simpleExpression instanceof EntityLiteral) { + // This is a special case where the mapping i.e. attribute name matches an entity name + try { + Attribute attribute = managedType.getAttribute(expression); + return Collections.singletonList(new ScalarTargetResolvingExpressionVisitor.TargetTypeImpl(false, attribute, attribute.getJavaType(), null, attribute.getJavaType())); + } catch (IllegalArgumentException ex) { + // Apparently it's not an attribute, so let it run through + } + } ScalarTargetResolvingExpressionVisitor visitor = new ScalarTargetResolvingExpressionVisitor(managedType, entityMetamodel, jpqlFunctions, rootTypes); simpleExpression.accept(visitor); return visitor.getPossibleTargetTypes(); diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/basic/AttributeMatchesEntityNameTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/basic/AttributeMatchesEntityNameTest.java new file mode 100644 index 0000000000..30dfe44889 --- /dev/null +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/basic/AttributeMatchesEntityNameTest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2014 - 2021 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.view.testsuite.basic; + +import com.blazebit.persistence.testsuite.entity.Document; +import com.blazebit.persistence.testsuite.entity.DocumentHolder; +import com.blazebit.persistence.view.EntityView; +import com.blazebit.persistence.view.EntityViewManager; +import com.blazebit.persistence.view.IdMapping; +import com.blazebit.persistence.view.Mapping; +import com.blazebit.persistence.view.testsuite.AbstractEntityViewTest; +import org.junit.Before; +import org.junit.Test; + +/** + * + * @author Christian Beikov + * @since 1.6.0 + */ +public class AttributeMatchesEntityNameTest extends AbstractEntityViewTest { + + protected EntityViewManager evm; + + @Override + protected Class[] getEntityClasses() { + return concat(super.getEntityClasses(), DocumentHolder.class); + } + + @Before + public void initEvm() { + evm = build( + DocumentHolderView.class, + DocumentIdView.class + ); + } + + @EntityView(DocumentHolder.class) + public interface DocumentHolderView { + @IdMapping + Long getId(); + @Mapping("Document") + DocumentIdView getDocument(); + } + + @EntityView(Document.class) + public interface DocumentIdView { + @IdMapping + Long getId(); + } + + // Test for https://github.com/Blazebit/blaze-persistence/issues/1294 + @Test + public void testFetch() { + evm.find(em, DocumentHolderView.class, 1L); + } +}