| /* |
| * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved. |
| * Copyright (c) 2021 IBM Corporation. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // Contributors: |
| // Oracle - initial API and implementation |
| // |
| package org.eclipse.persistence.jpa.jpql.tools.resolver; |
| |
| import static org.eclipse.persistence.jpa.jpql.LiteralType.PATH_EXPRESSION_LAST_PATH; |
| import static org.eclipse.persistence.jpa.jpql.LiteralType.RESULT_VARIABLE; |
| |
| import java.lang.annotation.Annotation; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import org.eclipse.persistence.jpa.jpql.ExpressionTools; |
| import org.eclipse.persistence.jpa.jpql.parser.AbsExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.AbstractEclipseLinkExpressionVisitor; |
| import org.eclipse.persistence.jpa.jpql.parser.AdditionExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.AvgFunction; |
| import org.eclipse.persistence.jpa.jpql.parser.CollectionExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.ConcatExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.CountFunction; |
| import org.eclipse.persistence.jpa.jpql.parser.DivisionExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.Expression; |
| import org.eclipse.persistence.jpa.jpql.parser.IndexExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.LengthExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.LocateExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.LowerExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.MaxFunction; |
| import org.eclipse.persistence.jpa.jpql.parser.MinFunction; |
| import org.eclipse.persistence.jpa.jpql.parser.ResultVariable; |
| import org.eclipse.persistence.jpa.jpql.parser.SimpleSelectClause; |
| import org.eclipse.persistence.jpa.jpql.parser.SimpleSelectStatement; |
| import org.eclipse.persistence.jpa.jpql.parser.SizeExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.SqrtExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.StateFieldPathExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.SubstringExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.SubtractionExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.SumFunction; |
| import org.eclipse.persistence.jpa.jpql.parser.TrimExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.UpperExpression; |
| import org.eclipse.persistence.jpa.jpql.tools.JPQLQueryContext; |
| import org.eclipse.persistence.jpa.jpql.tools.spi.IManagedType; |
| import org.eclipse.persistence.jpa.jpql.tools.spi.IManagedTypeProvider; |
| import org.eclipse.persistence.jpa.jpql.tools.spi.IManagedTypeVisitor; |
| import org.eclipse.persistence.jpa.jpql.tools.spi.IMapping; |
| import org.eclipse.persistence.jpa.jpql.tools.spi.IMappingType; |
| import org.eclipse.persistence.jpa.jpql.tools.spi.IType; |
| import org.eclipse.persistence.jpa.jpql.tools.spi.ITypeDeclaration; |
| import org.eclipse.persistence.jpa.jpql.tools.utility.iterable.SnapshotCloneIterable; |
| |
| /** |
| * This {@link Resolver} wraps a subquery that is used as the "root" object in a query's declaration. |
| * <p> |
| * Example: |
| * |
| * <pre><code> SELECT e.firstName |
| * FROM Employee e, (SELECT count(e2), e2.firstName FROM Employee e2) e3 |
| * WHERE e.firstName = e3.firstName</code></pre> |
| * |
| * @version 2.5 |
| * @since 2.4 |
| * @author Pascal Filion |
| */ |
| public class FromSubqueryResolver extends Resolver { |
| |
| /** |
| * The virtual {@link IManagedType} representing the subquery. |
| */ |
| private IManagedType managedType; |
| |
| /** |
| * The {@link JPQLQueryContext} is used to query information about the application metadata and |
| * cached information. |
| */ |
| private JPQLQueryContext queryContext; |
| |
| /** |
| * The subquery being defined as a "root" object. |
| */ |
| private SimpleSelectStatement subquery; |
| |
| /** |
| * Creates a new <code>FromSubqueryResolver</code>. |
| * |
| * @param parent The parent of this resolver, which is never <code>null</code> |
| * @param queryContext The context used to query information about the application metadata and |
| * cached information |
| * @exception NullPointerException If the parent is <code>null</code> |
| */ |
| public FromSubqueryResolver(Resolver parent, |
| JPQLQueryContext queryContext, |
| SimpleSelectStatement subquery) { |
| |
| super(parent); |
| this.subquery = subquery; |
| this.queryContext = queryContext; |
| } |
| |
| @Override |
| protected IType buildType() { |
| return getManagedType().getType(); |
| } |
| |
| @Override |
| protected ITypeDeclaration buildTypeDeclaration() { |
| return getType().getTypeDeclaration(); |
| } |
| |
| @Override |
| public IManagedType getManagedType() { |
| if (managedType == null) { |
| managedType = new VirtualManagedType(); |
| } |
| return managedType; |
| } |
| |
| private enum MappingType { |
| PROPERTY, |
| RELATIONSHIP, |
| UNKNOWN |
| } |
| |
| /** |
| * This {@link IManagedType} represents a virtual managed type where its content will be derived |
| * from the subquery. |
| */ |
| protected class VirtualManagedType implements IManagedType { |
| |
| /** |
| * The virtual {@link IMapping mappings} representing what is selected. |
| */ |
| private Map<String, IMapping> mappings; |
| |
| @Override |
| public void accept(IManagedTypeVisitor visitor) { |
| } |
| |
| @Override |
| public int compareTo(IManagedType managedType) { |
| return getType().getName().compareTo(managedType.getType().getName()); |
| } |
| |
| @Override |
| public IMapping getMappingNamed(String name) { |
| initializeMappings(); |
| return mappings.get(name); |
| } |
| |
| @Override |
| public IManagedTypeProvider getProvider() { |
| return FromSubqueryResolver.this.getProvider(); |
| } |
| |
| @Override |
| public IType getType() { |
| return getProvider().getTypeRepository().getType(IType.UNRESOLVABLE_TYPE); |
| } |
| |
| private void initializeMappings() { |
| |
| if (mappings == null) { |
| mappings = new HashMap<String, IMapping>(); |
| |
| // Create virtual mappings that wraps the select items |
| VirtualMappingBuilder builder = new VirtualMappingBuilder(); |
| builder.parent = this; |
| builder.mappings = mappings; |
| |
| subquery.accept(builder); |
| } |
| } |
| |
| @Override |
| public Iterable<IMapping> mappings() { |
| initializeMappings(); |
| return new SnapshotCloneIterable<IMapping>(mappings.values()); |
| } |
| } |
| |
| /** |
| * This virtual {@link IMapping} wraps one of the select items. |
| */ |
| protected static class VirtualMapping implements IMapping { |
| |
| private MappingType mappingType; |
| private String name; |
| private IManagedType parent; |
| private Resolver resolver; |
| |
| protected VirtualMapping(IManagedType parent, |
| String name, |
| Resolver resolver, |
| MappingType mappingType) { |
| |
| super(); |
| this.name = name; |
| this.parent = parent; |
| this.resolver = resolver; |
| this.mappingType = mappingType; |
| } |
| |
| @Override |
| public int compareTo(IMapping mapping) { |
| return getName().compareTo(mapping.getName()); |
| } |
| |
| @Override |
| public int getMappingType() { |
| IMapping mapping = resolver.getMapping(); |
| return (mapping != null) ? mapping.getMappingType() : IMappingType.TRANSIENT; |
| } |
| |
| @Override |
| public String getName() { |
| return name; |
| } |
| |
| @Override |
| public IManagedType getParent() { |
| return parent; |
| } |
| |
| @Override |
| public IType getType() { |
| return resolver.getType(); |
| } |
| |
| @Override |
| public ITypeDeclaration getTypeDeclaration() { |
| return resolver.getTypeDeclaration(); |
| } |
| |
| @Override |
| public boolean hasAnnotation(Class<? extends Annotation> annotationType) { |
| // TODO: Can we do this??? |
| return getType().hasAnnotation(annotationType); |
| } |
| |
| @Override |
| public boolean isCollection() { |
| IMapping mapping = resolver.getMapping(); |
| return (mapping != null) ? mapping.isCollection() : false; |
| } |
| |
| @Override |
| public boolean isEmbeddable() { |
| IMapping mapping = resolver.getMapping(); |
| return (mapping != null) ? mapping.isEmbeddable() : false; |
| } |
| |
| @Override |
| public boolean isProperty() { |
| IMapping mapping = resolver.getMapping(); |
| return (mapping != null) ? mapping.isProperty() : (mappingType == MappingType.PROPERTY); |
| } |
| |
| @Override |
| public boolean isRelationship() { |
| IMapping mapping = resolver.getMapping(); |
| return (mapping != null) ? mapping.isRelationship() : (mappingType == MappingType.RELATIONSHIP); |
| } |
| |
| @Override |
| public boolean isTransient() { |
| IMapping mapping = resolver.getMapping(); |
| return (mapping != null) ? mapping.isTransient() : false; |
| } |
| |
| @Override |
| public String toString() { |
| return name; |
| } |
| } |
| |
| /** |
| * This visitor will traverse the <code><b>SELECT</b></code> clause and create virtual mappings |
| * for the state field path expressions and any expression aliased with a result variable. |
| */ |
| protected class VirtualMappingBuilder extends AbstractEclipseLinkExpressionVisitor { |
| |
| /** |
| * The virtual {@link IMapping} that wraps a select item mapped with the result variable if |
| * one was defined otherwise the name will be generated based on the type of select item. |
| */ |
| private Map<String, IMapping> mappings; |
| |
| /** |
| * |
| */ |
| private MappingType mappingType; |
| |
| /** |
| * The virtual {@link IManagedType}. |
| */ |
| protected IManagedType parent; |
| |
| /** |
| * Creates a new <code>VirtualMappingBuilder</code>. |
| */ |
| public VirtualMappingBuilder() { |
| super(); |
| mappingType = MappingType.UNKNOWN; |
| } |
| |
| /** |
| * Creates |
| * |
| */ |
| protected IMapping buildMapping(String name, Resolver resolver) { |
| return new VirtualMapping(parent, name, resolver, mappingType); |
| } |
| |
| @Override |
| public void visit(AbsExpression expression) { |
| mappingType = MappingType.PROPERTY; |
| } |
| |
| @Override |
| public void visit(AdditionExpression expression) { |
| mappingType = MappingType.PROPERTY; |
| } |
| |
| @Override |
| public void visit(AvgFunction expression) { |
| mappingType = MappingType.PROPERTY; |
| } |
| |
| @Override |
| public void visit(CollectionExpression expression) { |
| expression.acceptChildren(this); |
| } |
| |
| @Override |
| public void visit(ConcatExpression expression) { |
| mappingType = MappingType.PROPERTY; |
| } |
| |
| @Override |
| public void visit(CountFunction expression) { |
| mappingType = MappingType.PROPERTY; |
| } |
| |
| @Override |
| public void visit(DivisionExpression expression) { |
| mappingType = MappingType.PROPERTY; |
| } |
| |
| @Override |
| public void visit(IndexExpression expression) { |
| mappingType = MappingType.PROPERTY; |
| } |
| |
| @Override |
| public void visit(LengthExpression expression) { |
| mappingType = MappingType.PROPERTY; |
| } |
| |
| @Override |
| public void visit(LocateExpression expression) { |
| mappingType = MappingType.PROPERTY; |
| } |
| |
| @Override |
| public void visit(LowerExpression expression) { |
| mappingType = MappingType.PROPERTY; |
| } |
| |
| @Override |
| public void visit(MaxFunction expression) { |
| mappingType = MappingType.PROPERTY; |
| } |
| |
| @Override |
| public void visit(MinFunction expression) { |
| mappingType = MappingType.PROPERTY; |
| } |
| |
| @Override |
| public void visit(ResultVariable expression) { |
| |
| String name = queryContext.literal(expression, RESULT_VARIABLE); |
| |
| if (ExpressionTools.stringIsNotEmpty(name)) { |
| |
| // Visit the select expression in order to get a mapping type that could |
| // help determine the mapping type |
| Expression selectExpression = expression.getSelectExpression(); |
| selectExpression.accept(this); |
| |
| // Retrieve the select expression's Resolver and wrap it with a virtual mapping |
| Resolver resolver = queryContext.getResolver(selectExpression); |
| mappings.put(name, buildMapping(name, resolver)); |
| } |
| } |
| |
| @Override |
| public void visit(SimpleSelectClause expression) { |
| expression.getSelectExpression().accept(this); |
| } |
| |
| @Override |
| public void visit(SimpleSelectStatement expression) { |
| expression.getSelectClause().accept(this); |
| } |
| |
| @Override |
| public void visit(SizeExpression expression) { |
| mappingType = MappingType.PROPERTY; |
| } |
| |
| @Override |
| public void visit(SqrtExpression expression) { |
| mappingType = MappingType.PROPERTY; |
| } |
| |
| @Override |
| public void visit(StateFieldPathExpression expression) { |
| |
| if (!expression.startsWithDot()) { |
| String name = queryContext.literal(expression, PATH_EXPRESSION_LAST_PATH); |
| |
| if (ExpressionTools.stringIsNotEmpty(name)) { |
| |
| mappingType = MappingType.UNKNOWN; |
| |
| Resolver resolver = queryContext.getResolver(expression); |
| mappings.put(name, buildMapping(name, resolver)); |
| } |
| } |
| } |
| |
| @Override |
| public void visit(SubstringExpression expression) { |
| mappingType = MappingType.PROPERTY; |
| } |
| |
| @Override |
| public void visit(SubtractionExpression expression) { |
| mappingType = MappingType.PROPERTY; |
| } |
| |
| @Override |
| public void visit(SumFunction expression) { |
| mappingType = MappingType.PROPERTY; |
| } |
| |
| @Override |
| public void visit(TrimExpression expression) { |
| mappingType = MappingType.PROPERTY; |
| } |
| |
| @Override |
| public void visit(UpperExpression expression) { |
| mappingType = MappingType.PROPERTY; |
| } |
| } |
| } |