| /* |
| * Copyright (c) 2006, 2021 Oracle and/or its affiliates. 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 |
| // |
| // 09/02/2019-3.0 Alexandre Jacob |
| // - 527415: Fix code when locale is tr, az or lt |
| package org.eclipse.persistence.jpa.jpql.tools.resolver; |
| |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.persistence.jpa.jpql.ExpressionTools; |
| import org.eclipse.persistence.jpa.jpql.LiteralType; |
| import org.eclipse.persistence.jpa.jpql.parser.AbstractExpressionVisitor; |
| import org.eclipse.persistence.jpa.jpql.parser.AbstractSchemaName; |
| import org.eclipse.persistence.jpa.jpql.parser.AnonymousExpressionVisitor; |
| import org.eclipse.persistence.jpa.jpql.parser.CollectionExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.CollectionMemberDeclaration; |
| import org.eclipse.persistence.jpa.jpql.parser.CollectionValuedPathExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.DeleteClause; |
| import org.eclipse.persistence.jpa.jpql.parser.DeleteStatement; |
| import org.eclipse.persistence.jpa.jpql.parser.Expression; |
| import org.eclipse.persistence.jpa.jpql.parser.FromClause; |
| import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariable; |
| import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariableDeclaration; |
| import org.eclipse.persistence.jpa.jpql.parser.JPQLExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.Join; |
| import org.eclipse.persistence.jpa.jpql.parser.NullExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.RangeVariableDeclaration; |
| import org.eclipse.persistence.jpa.jpql.parser.ResultVariable; |
| import org.eclipse.persistence.jpa.jpql.parser.SelectClause; |
| import org.eclipse.persistence.jpa.jpql.parser.SelectStatement; |
| import org.eclipse.persistence.jpa.jpql.parser.SimpleFromClause; |
| import org.eclipse.persistence.jpa.jpql.parser.SimpleSelectClause; |
| import org.eclipse.persistence.jpa.jpql.parser.SimpleSelectStatement; |
| import org.eclipse.persistence.jpa.jpql.parser.SubExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.UpdateClause; |
| import org.eclipse.persistence.jpa.jpql.parser.UpdateStatement; |
| import org.eclipse.persistence.jpa.jpql.tools.JPQLQueryContext; |
| import org.eclipse.persistence.jpa.jpql.tools.spi.IMapping; |
| import org.eclipse.persistence.jpa.jpql.tools.spi.IQuery; |
| import org.eclipse.persistence.jpa.jpql.tools.spi.IType; |
| import org.eclipse.persistence.jpa.jpql.tools.spi.ITypeDeclaration; |
| |
| /** |
| * This {@link Resolver} is responsible to visit the current query (which is either the top-level |
| * query or a subquery) and gathers the information from the declaration clause. For a <b>SELECT</b> |
| * or <b>DELETE</b> clause, the information will be retrieved from the <b>FROM</b> clause. For an |
| * <code>UDPATE</code> clause, it will be retrieved from the unique identification range variable |
| * declaration. |
| * |
| * @version 2.5 |
| * @since 2.3 |
| * @author Pascal Filion |
| */ |
| public class DeclarationResolver extends Resolver { |
| |
| /** |
| * The {@link Declaration Declarations} of the current query that was parsed. |
| */ |
| private List<Declaration> declarations; |
| |
| /** |
| * This visitor is responsible to visit the current query's declaration and populate this |
| * resolver with the list of declarations. |
| */ |
| private DeclarationVisitor declarationVisitor; |
| |
| /** |
| * This visitor is responsible to convert the abstract schema name into a path expression. |
| */ |
| private QualifyRangeDeclarationVisitor qualifyRangeDeclarationVisitor; |
| |
| /** |
| * The context used to query information about the query. |
| */ |
| private JPQLQueryContext queryContext; |
| |
| /** |
| * The identification variable names mapped to their resolvers. |
| */ |
| private Map<String, Resolver> resolvers; |
| |
| /** |
| * The variables identifying the select expressions, if any was defined or an empty set if none |
| * were defined. |
| */ |
| private Map<IdentificationVariable, String> resultVariables; |
| |
| /** |
| * This visitor resolves the "root" object represented by the given {@link Expression}. This will |
| * also handle using a subquery in the <code><b>FROM</b></code> clause. This is only supported by |
| * EclipseLink. |
| */ |
| private RootObjectExpressionVisitor rootObjectExpressionVisitor; |
| |
| /** |
| * Creates a new <code>DeclarationResolver</code>. |
| * |
| * @param parent The parent resolver if this is used for a subquery or null if it's used for the |
| * top-level query |
| * @param queryContext The context used to query information about the query |
| */ |
| public DeclarationResolver(DeclarationResolver parent, JPQLQueryContext queryContext) { |
| super(parent); |
| initialize(queryContext); |
| } |
| |
| /** |
| * Adds the given {@link Declaration} at the end of the list. |
| * |
| * @param declaration The {@link Declaration} representing a single variable declaration |
| */ |
| protected final void addDeclaration(Declaration declaration) { |
| declarations.add(declaration); |
| } |
| |
| /** |
| * Registers a range variable declaration that will be used when a JPQL fragment is parsed. |
| * |
| * @param entityName The name of the entity to be accessible with the given variable name |
| * @param variableName The identification variable used to navigate to the entity |
| */ |
| public void addRangeVariableDeclaration(String entityName, String variableName) { |
| |
| // Always make the identification variable be upper case since it's |
| // case insensitive, the get will also use upper case |
| String internalVariableName = variableName.toUpperCase(Locale.ROOT); |
| |
| // Make sure the identification variable was not declared more than once, |
| // this could cause issues when trying to resolve it |
| if (!resolvers.containsKey(internalVariableName)) { |
| |
| // Resolve the expression and map it with the identification variable |
| Resolver resolver = new EntityResolver(this, entityName); |
| resolver = new IdentificationVariableResolver(resolver, variableName); |
| resolvers.put(internalVariableName, resolver); |
| |
| RangeDeclaration declaration = new RangeDeclaration(); |
| declaration.rootPath = entityName; |
| declaration.identificationVariable = new IdentificationVariable(null, variableName); |
| declarations.add(declaration); |
| } |
| } |
| |
| protected DeclarationVisitor buildDeclarationVisitor() { |
| return new DeclarationVisitor(); |
| } |
| |
| protected RootObjectExpressionVisitor buildRootObjectExpressionVisitor() { |
| return new RootObjectExpressionVisitor(); |
| } |
| |
| @Override |
| protected IType buildType() { |
| return getTypeHelper().unknownType(); |
| } |
| |
| @Override |
| protected ITypeDeclaration buildTypeDeclaration() { |
| return getTypeHelper().unknownTypeDeclaration(); |
| } |
| |
| @Override |
| protected void checkParent(Resolver parent) { |
| // Don't do anything, this is the root |
| } |
| |
| /** |
| * Converts the given {@link Declaration} from being set as a range variable declaration to |
| * a path expression declaration. |
| * <p> |
| * In this query "<code>UPDATE Employee SET firstName = 'MODIFIED' WHERE (SELECT COUNT(m) FROM |
| * managedEmployees m) {@literal >} 0</code>" <em>managedEmployees</em> is an unqualified |
| * collection-valued path expression (<code>employee.managedEmployees</code>). |
| * |
| * @param declaration The {@link Declaration} that was parsed to range over an abstract schema |
| * name but is actually ranging over a path expression |
| * @param outerVariableName The identification variable coming from the parent identification |
| * variable declaration |
| */ |
| public void convertUnqualifiedDeclaration(Declaration declaration, String outerVariableName) { |
| |
| QualifyRangeDeclarationVisitor visitor = qualifyRangeDeclarationVisitor(); |
| |
| try { |
| visitor.oldDeclaration = declaration; |
| visitor.outerVariableName = outerVariableName; |
| |
| declaration.declarationExpression.accept(visitor); |
| } |
| finally { |
| visitor.oldDeclaration = null; |
| visitor.newDeclaration = null; |
| visitor.outerVariableName = null; |
| } |
| } |
| |
| /** |
| * Disposes the internal data. |
| */ |
| public void dispose() { |
| resolvers .clear(); |
| declarations .clear(); |
| resultVariables.clear(); |
| } |
| |
| /** |
| * Retrieves the {@link Declaration} for which the given variable name is used to navigate to the |
| * "root" object. |
| * |
| * @param variableName The name of the identification variable that is used to navigate a "root" object |
| * @return The {@link Declaration} containing the information about the identification variable declaration |
| * @since 2.5 |
| */ |
| public Declaration getDeclaration(String variableName) { |
| |
| for (Declaration declaration : declarations) { |
| if (declaration.getVariableName().equalsIgnoreCase(variableName)) { |
| return declaration; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Returns the ordered list of {@link Declaration Declarations}. |
| * |
| * @return The {@link Declaration Declarations} of the current query that was parsed |
| */ |
| public List<Declaration> getDeclarations() { |
| return declarations; |
| } |
| |
| protected DeclarationVisitor getDeclarationVisitor() { |
| if (declarationVisitor == null) { |
| declarationVisitor = buildDeclarationVisitor(); |
| } |
| return declarationVisitor; |
| } |
| |
| @Override |
| public DeclarationResolver getParent() { |
| return (DeclarationResolver) super.getParent(); |
| } |
| |
| @Override |
| public IQuery getQuery() { |
| return queryContext.getQuery(); |
| } |
| |
| /** |
| * Returns the {@link JPQLQueryContext} that is used by this visitor. |
| * |
| * @return The {@link JPQLQueryContext} holding onto the JPQL query and the cached information |
| */ |
| protected JPQLQueryContext getQueryContext() { |
| return queryContext; |
| } |
| |
| /** |
| * Retrieves the {@link Resolver} mapped with the given identification variable. If the |
| * identification is not defined in the declaration traversed by this resolver, than the search |
| * will traverse the parent hierarchy. |
| * |
| * @param variableName The identification variable that maps a {@link Resolver} |
| * @return The {@link Resolver} mapped with the given identification variable |
| */ |
| public Resolver getResolver(String variableName) { |
| |
| variableName = variableName.toUpperCase(Locale.ROOT); |
| Resolver resolver = getResolverImp(variableName); |
| |
| if ((resolver == null) && (getParent() != null)) { |
| resolver = getParent().getResolver(variableName); |
| } |
| |
| if (resolver == null) { |
| resolver = new NullResolver(this); |
| resolvers.put(variableName, resolver); |
| } |
| |
| return resolver; |
| } |
| |
| /** |
| * Retrieves the {@link Resolver} mapped with the given identification variable. The search does |
| * not traverse the parent hierarchy. |
| * |
| * @param variableName The identification variable that maps a {@link Resolver} |
| * @return The {@link Resolver} mapped with the given identification variable |
| */ |
| protected Resolver getResolverImp(String variableName) { |
| return resolvers.get(variableName); |
| } |
| |
| /** |
| * Returns the variables that got defined in the select expression. This only applies to JPQL |
| * queries built for JPA 2.0 or later. |
| * |
| * @return The variables identifying the select expressions, if any was defined or an empty set |
| * if none were defined |
| */ |
| public Set<String> getResultVariables() { |
| return new HashSet<>(resultVariables.values()); |
| } |
| |
| /** |
| * Returns the map of result variables that got used to define a select expression. This only |
| * applies to JPQL queries built for JPA 2.0. |
| * |
| * @return The variables identifying the select expressions, if any was defined or an empty map |
| * if none were defined |
| */ |
| public Map<IdentificationVariable, String> getResultVariablesMap() { |
| return resultVariables; |
| } |
| |
| protected RootObjectExpressionVisitor getRootObjectExpressionVisitor() { |
| if (rootObjectExpressionVisitor == null) { |
| rootObjectExpressionVisitor = buildRootObjectExpressionVisitor(); |
| } |
| return rootObjectExpressionVisitor; |
| } |
| |
| /** |
| * Determines whether the JPQL expression has <b>JOIN</b> expressions. |
| * |
| * @return <code>true</code> if the query or subquery being traversed contains <b>JOIN</b> |
| * expressions; <code>false</code> otherwise |
| */ |
| public boolean hasJoins() { |
| |
| for (Declaration declaration : declarations) { |
| if (declaration.hasJoins()) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Initializes this <code>DeclarationResolver</code>. |
| * |
| * @param queryContext The context used to query information about the query |
| */ |
| protected void initialize(JPQLQueryContext queryContext) { |
| this.queryContext = queryContext; |
| this.resolvers = new HashMap<>(); |
| this.declarations = new LinkedList<>(); |
| this.resultVariables = new HashMap<>(); |
| } |
| |
| /** |
| * Determines whether the given identification variable is defining a <b>JOIN</b> expression or |
| * in a <code>IN</code> expressions for a collection-valued field. If the search didn't find the |
| * identification in this resolver, then it will traverse the parent hierarchy. |
| * |
| * @param variableName The identification variable to check for what it maps |
| * @return <code>true</code> if the given identification variable maps a collection-valued field |
| * defined in a <code>JOIN</code> or <code>IN</code> expression; <code>false</code> otherwise |
| */ |
| public boolean isCollectionIdentificationVariable(String variableName) { |
| |
| boolean result = isCollectionIdentificationVariableImp(variableName); |
| |
| if (!result && (getParent() != null)) { |
| result = getParent().isCollectionIdentificationVariableImp(variableName); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Determines whether the given identification variable is defining a <b>JOIN</b> expression or |
| * in a <code>IN</code> expressions for a collection-valued field. The search does not traverse |
| * the parent hierarchy. |
| * |
| * @param variableName The identification variable to check for what it maps |
| * @return <code>true</code> if the given identification variable maps a collection-valued field |
| * defined in a <code>JOIN</code> or <code>IN</code> expression; <code>false</code> otherwise |
| */ |
| @SuppressWarnings("fallthrough") |
| protected boolean isCollectionIdentificationVariableImp(String variableName) { |
| |
| for (Declaration declaration : declarations) { |
| |
| switch (declaration.getType()) { |
| |
| case COLLECTION: { |
| if (declaration.getVariableName().equalsIgnoreCase(variableName)) { |
| return true; |
| } |
| return false; |
| } |
| |
| case RANGE: |
| case DERIVED: { |
| |
| AbstractRangeDeclaration rangeDeclaration = (AbstractRangeDeclaration) declaration; |
| |
| // Check the JOIN expressions |
| for (Join join : rangeDeclaration.getJoins()) { |
| |
| String joinVariableName = queryContext.literal( |
| join.getIdentificationVariable(), |
| LiteralType.IDENTIFICATION_VARIABLE |
| ); |
| |
| if (joinVariableName.equalsIgnoreCase(variableName)) { |
| // Make sure the JOIN expression maps a collection mapping |
| Resolver resolver = getResolver(variableName); |
| IMapping mapping = (resolver != null) ? resolver.getMapping() : null; |
| return (mapping != null) && mapping.isCollection(); |
| } |
| } |
| } |
| |
| default: |
| continue; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Determines whether the given variable name is an identification variable name mapping an |
| * entity. The search traverses the parent resolver if it is not found in this resolver, which |
| * represents the current JPQL query. |
| * |
| * @param variableName The name of the variable to verify if it's defined in a range variable |
| * declaration in the current query or any parent query |
| * @return <code>true</code> if the variable name is mapping an abstract schema name; <code>false</code> |
| * if it's defined in a collection member declaration |
| */ |
| public boolean isRangeIdentificationVariable(String variableName) { |
| |
| boolean result = isRangeIdentificationVariableImp(variableName); |
| |
| if (!result && (getParent() != null)) { |
| result = getParent().isRangeIdentificationVariableImp(variableName); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Determines whether the given variable name is an identification variable name mapping an |
| * entity. The search only searches in this resolver, which represents the current JPQL query. |
| * |
| * @param variableName The name of the variable to verify if it's defined in a range variable |
| * declaration in the current query or any parent query |
| * @return <code>true</code> if the variable name is mapping an abstract schema name; <code>false</code> |
| * if it's defined in a collection member declaration |
| */ |
| protected boolean isRangeIdentificationVariableImp(String variableName) { |
| |
| for (Declaration declaration : declarations) { |
| |
| if (declaration.getType().isRange() && |
| declaration.getVariableName().equalsIgnoreCase(variableName)) { |
| |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Determines if the given variable is a result variable. |
| * |
| * @param variable The variable to check if it's a result variable |
| * @return <code>true</code> if the given variable is defined as a result variable; <code>false</code> |
| * otherwise |
| */ |
| public boolean isResultVariable(String variable) { |
| return resultVariables.containsValue(variable.toUpperCase(Locale.ROOT)); |
| } |
| |
| /** |
| * Visits the current query (which is either the top-level query or a subquery) and gathers the |
| * information from the declaration clause. |
| * |
| * @param expression The {@link Expression} to visit in order to retrieve the information |
| * contained in the given query's declaration |
| */ |
| public void populate(Expression expression) { |
| DeclarationVisitor visitor = getDeclarationVisitor(); |
| try { |
| expression.accept(visitor); |
| } |
| finally { |
| visitor.currentDeclaration = null; |
| } |
| } |
| |
| protected QualifyRangeDeclarationVisitor qualifyRangeDeclarationVisitor() { |
| if (qualifyRangeDeclarationVisitor == null) { |
| qualifyRangeDeclarationVisitor = new QualifyRangeDeclarationVisitor(); |
| } |
| return qualifyRangeDeclarationVisitor; |
| } |
| |
| /** |
| * Resolves the "root" object represented by the given {@link Expression}. This will also handle |
| * using a subquery in the <code><b>FROM</b></code> clause. This is only supported by EclipseLink. |
| * |
| * @param expression The {@link Expression} to visit, which represents the "root" object of a |
| * declaration |
| * @return The {@link Resolver} for the given {@link Expression} |
| */ |
| protected Resolver resolveRootObject(Expression expression) { |
| RootObjectExpressionVisitor visitor = getRootObjectExpressionVisitor(); |
| try { |
| expression.accept(visitor); |
| return visitor.resolver; |
| } |
| finally { |
| visitor.resolver = null; |
| } |
| } |
| |
| protected String visitDeclaration(Expression expression, Expression identificationVariable) { |
| |
| // Visit the identification variable expression and retrieve the identification variable name |
| String variableName = queryContext.literal( |
| identificationVariable, |
| LiteralType.IDENTIFICATION_VARIABLE |
| ); |
| |
| // If it's not empty, then we can create a Resolver |
| if (variableName != ExpressionTools.EMPTY_STRING) { |
| |
| // Always make the identification variable be upper case since it's |
| // case insensitive, the get will also use upper case |
| String internalVariableName = variableName.toUpperCase(Locale.ROOT); |
| |
| // Make sure the identification variable was not declared more than once, |
| // this could cause issues when trying to resolve it |
| if (!resolvers.containsKey(internalVariableName)) { |
| |
| // Resolve the expression and map it with the identification variable |
| Resolver resolver = resolveRootObject(expression); |
| resolver = new IdentificationVariableResolver(resolver, variableName); |
| resolvers.put(internalVariableName, resolver); |
| } |
| |
| return variableName; |
| } |
| |
| return ExpressionTools.EMPTY_STRING; |
| } |
| |
| protected class DeclarationVisitor extends AbstractExpressionVisitor { |
| |
| /** |
| * This flag is used to determine what to do in {@link #visit(SimpleSelectStatement)}. |
| */ |
| protected boolean buildingDeclaration; |
| |
| /** |
| * Flag used to determine if the {@link IdentificationVariable} to visit is a result variable. |
| */ |
| protected boolean collectResultVariable; |
| |
| /** |
| * The {@link Declaration} being populated. |
| */ |
| protected Declaration currentDeclaration; |
| |
| @Override |
| public void visit(AbstractSchemaName expression) { |
| currentDeclaration = new RangeDeclaration(); |
| currentDeclaration.rootPath = expression.getText(); |
| declarations.add(currentDeclaration); |
| } |
| |
| @Override |
| public void visit(CollectionExpression expression) { |
| expression.acceptChildren(this); |
| } |
| |
| @Override |
| public void visit(CollectionMemberDeclaration expression) { |
| |
| CollectionDeclaration declaration = new CollectionDeclaration(); |
| declaration.declarationExpression = expression; |
| declaration.baseExpression = expression.getCollectionValuedPathExpression(); |
| declaration.rootPath = declaration.baseExpression.toActualText(); |
| declarations.add(declaration); |
| |
| // Retrieve the IdentificationVariable |
| Expression identificationVariable = expression.getIdentificationVariable(); |
| String variableName = visitDeclaration(expression, identificationVariable); |
| |
| if (variableName != ExpressionTools.EMPTY_STRING) { |
| declaration.identificationVariable = (IdentificationVariable) identificationVariable; |
| } |
| } |
| |
| @Override |
| public void visit(CollectionValuedPathExpression expression) { |
| currentDeclaration = new DerivedDeclaration(); |
| currentDeclaration.rootPath = expression.toActualText(); |
| declarations.add(currentDeclaration); |
| } |
| |
| @Override |
| public void visit(DeleteClause expression) { |
| |
| try { |
| buildingDeclaration = true; |
| expression.getRangeVariableDeclaration().accept(this); |
| buildingDeclaration = false; |
| |
| currentDeclaration.declarationExpression = expression; |
| } |
| finally { |
| currentDeclaration = null; |
| } |
| } |
| |
| @Override |
| public void visit(DeleteStatement expression) { |
| expression.getDeleteClause().accept(this); |
| } |
| |
| @Override |
| public void visit(FromClause expression) { |
| expression.getDeclaration().accept(this); |
| } |
| |
| @Override |
| public void visit(IdentificationVariable expression) { |
| if (collectResultVariable) { |
| resultVariables.put(expression, expression.getText().toUpperCase(Locale.ROOT)); |
| } |
| } |
| |
| @Override |
| public void visit(IdentificationVariableDeclaration expression) { |
| |
| try { |
| expression.getRangeVariableDeclaration().accept(this); |
| expression.getJoins().accept(this); |
| currentDeclaration.declarationExpression = expression; |
| } |
| finally { |
| currentDeclaration = null; |
| } |
| } |
| |
| @Override |
| public void visit(Join expression) { |
| |
| AbstractRangeDeclaration rangeDeclaration = (AbstractRangeDeclaration) currentDeclaration; |
| Expression identificationVariable = expression.getIdentificationVariable(); |
| visitDeclaration(expression, identificationVariable); |
| rangeDeclaration.addJoin(expression); |
| } |
| |
| @Override |
| public void visit(JPQLExpression expression) { |
| expression.getQueryStatement().accept(this); |
| } |
| |
| @Override |
| public void visit(NullExpression expression) { |
| if (buildingDeclaration) { |
| currentDeclaration = new UnknownDeclaration(); |
| currentDeclaration.baseExpression = expression; |
| currentDeclaration.rootPath = ExpressionTools.EMPTY_STRING; |
| declarations.add(currentDeclaration); |
| } |
| } |
| |
| @Override |
| public void visit(RangeVariableDeclaration expression) { |
| |
| // Visit the "root" object, which will create the right Declaration |
| Expression rootObject = expression.getRootObject(); |
| buildingDeclaration = true; |
| rootObject.accept(this); |
| buildingDeclaration = false; |
| |
| // Set the base expression |
| currentDeclaration.baseExpression = expression; |
| |
| // Set the identification variable |
| Expression identificationVariable = expression.getIdentificationVariable(); |
| String variableName = visitDeclaration(rootObject, identificationVariable); |
| |
| if (variableName != ExpressionTools.EMPTY_STRING) { |
| currentDeclaration.identificationVariable = (IdentificationVariable) identificationVariable; |
| } |
| } |
| |
| @Override |
| public void visit(ResultVariable expression) { |
| collectResultVariable = true; |
| expression.getResultVariable().accept(this); |
| collectResultVariable = false; |
| } |
| |
| @Override |
| public void visit(SelectClause expression) { |
| expression.getSelectExpression().accept(this); |
| } |
| |
| @Override |
| public void visit(SelectStatement expression) { |
| expression.getFromClause().accept(this); |
| expression.getSelectClause().accept(this); |
| } |
| |
| @Override |
| public void visit(SimpleFromClause expression) { |
| expression.getDeclaration().accept(this); |
| } |
| |
| @Override |
| public void visit(SimpleSelectClause expression) { |
| expression.getSelectExpression().accept(this); |
| } |
| |
| @Override |
| public void visit(SimpleSelectStatement expression) { |
| expression.getFromClause().accept(this); |
| } |
| |
| @Override |
| public void visit(UpdateClause expression) { |
| |
| try { |
| buildingDeclaration = true; |
| expression.getRangeVariableDeclaration().accept(this); |
| buildingDeclaration = false; |
| |
| currentDeclaration.declarationExpression = expression; |
| } |
| finally { |
| currentDeclaration = null; |
| } |
| } |
| |
| @Override |
| public void visit(UpdateStatement expression) { |
| expression.getUpdateClause().accept(this); |
| } |
| } |
| |
| protected static class QualifyRangeDeclarationVisitor extends AbstractExpressionVisitor { |
| |
| /** |
| * The new {@link Declaration}. |
| */ |
| protected Declaration newDeclaration; |
| |
| /** |
| * The {@link Declaration} being modified. |
| */ |
| protected Declaration oldDeclaration; |
| |
| /** |
| * The identification variable coming from the parent identification variable declaration. |
| */ |
| protected String outerVariableName; |
| |
| @Override |
| public void visit(CollectionValuedPathExpression expression) { |
| newDeclaration.rootPath = expression.toActualText(); |
| newDeclaration.baseExpression = expression; |
| } |
| |
| @Override |
| public void visit(IdentificationVariableDeclaration expression) { |
| expression.getRangeVariableDeclaration().accept(this); |
| } |
| |
| @Override |
| public void visit(RangeVariableDeclaration expression) { |
| |
| newDeclaration = new DerivedDeclaration(); |
| |
| expression.setVirtualIdentificationVariable(outerVariableName, newDeclaration.rootPath); |
| expression.getRootObject().accept(this); |
| } |
| } |
| |
| /** |
| * This visitor takes care to support a subquery defined as a "root" object. |
| */ |
| protected class RootObjectExpressionVisitor extends AnonymousExpressionVisitor { |
| |
| /** |
| * The {@link Resolver} of the "root" object. |
| */ |
| protected Resolver resolver; |
| |
| @Override |
| protected void visit(Expression expression) { |
| resolver = queryContext.getResolver(expression); |
| } |
| |
| @Override |
| public void visit(SimpleSelectStatement expression) { |
| resolver = new FromSubqueryResolver( |
| DeclarationResolver.this, |
| DeclarationResolver.this.queryContext, |
| expression |
| ); |
| } |
| |
| @Override |
| public void visit(SubExpression expression) { |
| expression.getExpression().accept(this); |
| } |
| } |
| } |