blob: c3aa868788159dbeaf79d4dc161b30015d4858fd [file] [log] [blame]
/*
* 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;
}
}
}