| /* |
| * Copyright (c) 1998, 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 from Oracle TopLink |
| package org.eclipse.persistence.internal.jpa.parsing; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| // TopLink imports |
| import org.eclipse.persistence.exceptions.JPQLException; |
| import org.eclipse.persistence.expressions.Expression; |
| import org.eclipse.persistence.expressions.ExpressionBuilder; |
| import org.eclipse.persistence.internal.expressions.ConstantExpression; |
| import org.eclipse.persistence.queries.ObjectLevelReadQuery; |
| import org.eclipse.persistence.queries.ReportQuery; |
| |
| /** |
| * INTERNAL |
| * <p><b>Purpose</b>: The node that represents typed variables, local variables, remote variables and TYPE constants. |
| * |
| * @author Jon Driscoll and Joel Lucuik |
| * @since TopLink 4.0 |
| */ |
| public class VariableNode extends Node implements AliasableNode { |
| |
| /** */ |
| private String variableName; |
| |
| /** */ |
| private String canonicalName; |
| |
| /** if this represents a type constant, this value will be populated by validate **/ |
| private Object classConstant = null; |
| |
| /** |
| * VariableNode constructor comment. |
| */ |
| public VariableNode() { |
| super(); |
| } |
| |
| public VariableNode(String newVariableName) { |
| setVariableName(newVariableName); |
| } |
| |
| public String getVariableName() { |
| return variableName; |
| } |
| |
| public void setVariableName(String newVariableName) { |
| variableName = newVariableName; |
| canonicalName = IdentificationVariableDeclNode.calculateCanonicalName(newVariableName); |
| } |
| |
| /** */ |
| public String getCanonicalVariableName() { |
| return canonicalName; |
| } |
| |
| /** |
| * INTERNAL |
| * Is this node a VariableNode |
| */ |
| @Override |
| public boolean isVariableNode() { |
| return true; |
| } |
| |
| /** |
| * INTERNAL |
| * Apply this node to the passed query |
| */ |
| @Override |
| public void applyToQuery(ObjectLevelReadQuery theQuery, GenerationContext generationContext) { |
| String name = getCanonicalVariableName(); |
| if (theQuery instanceof ReportQuery) { |
| ReportQuery reportQuery = (ReportQuery)theQuery; |
| Expression expression = generationContext.expressionFor(name); |
| if (expression == null) { |
| expression = generateExpression(generationContext); |
| } |
| addAttributeWithFetchJoins(reportQuery, expression, generationContext); |
| } else { |
| addFetchJoins(theQuery, generationContext); |
| } |
| } |
| |
| /** |
| * INTERNAL |
| * Add the variable as ReportQuery item. The method checks for any JOIN |
| * FETCH nodes of the current variable and adds them as part of the |
| * ReportQuery item. |
| */ |
| private void addAttributeWithFetchJoins(ReportQuery reportQuery, |
| Expression expression, |
| GenerationContext context) { |
| String name = getCanonicalVariableName(); |
| List<Node> fetchJoinNodes = context.getParseTreeContext().getFetchJoins(name); |
| if (fetchJoinNodes == null) { |
| reportQuery.addAttribute(name, expression); |
| } else { |
| List<Expression> fetchJoinExprs = new ArrayList<>(fetchJoinNodes.size()); |
| for (Iterator<Node> i = fetchJoinNodes.iterator(); i.hasNext(); ) { |
| Node node = i.next(); |
| fetchJoinExprs.add(node.generateExpression(context)); |
| } |
| reportQuery.addItem(name, expression, fetchJoinExprs); |
| } |
| } |
| |
| /** |
| * INTERNAL |
| * Check for any JOIN FETCH nodes of the current variable and add them as |
| * joined attributes. This method is called in case of a non ReportQuery |
| * instance. |
| */ |
| private void addFetchJoins(ObjectLevelReadQuery theQuery, |
| GenerationContext context) { |
| String name = getCanonicalVariableName(); |
| List<Node> fetchJoinNodes = context.getParseTreeContext().getFetchJoins(name); |
| if (fetchJoinNodes != null) { |
| for (Iterator<Node> i = fetchJoinNodes.iterator(); i.hasNext(); ) { |
| Node node = i.next(); |
| theQuery.addJoinedAttribute(node.generateExpression(context)); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL |
| * This node represent an unqualified field access in the case the method |
| * is called and the variableName is not defined as identification variable. |
| * The method returns a DotNode representing a qualified field access with |
| * the base variable as left child node. The right child node is an |
| * AttributeNode using the variableName as field name. |
| */ |
| @Override |
| public Node qualifyAttributeAccess(ParseTreeContext context) { |
| return context.isVariable(variableName) ? this : |
| (Node)context.getNodeFactory().newQualifiedAttribute( |
| getLine(), getColumn(), context.getBaseVariable(), variableName); |
| } |
| |
| /** |
| * INTERNAL |
| * Validate node and calculate its type. |
| */ |
| @Override |
| public void validate(ParseTreeContext context) { |
| TypeHelper typeHelper = context.getTypeHelper(); |
| classConstant = typeHelper.resolveSchema(variableName); |
| if (classConstant != null){ |
| setType(Class.class); |
| return; |
| } |
| String name = getCanonicalVariableName(); |
| if (context.isRangeVariable(name)) { |
| String schema = context.schemaForVariable(name); |
| setType(typeHelper.resolveSchema(schema)); |
| } else { |
| Node path = context.pathForVariable(name); |
| if (path == null) { |
| throw JPQLException.aliasResolutionException( |
| context.getQueryInfo(), getLine(), getColumn(), name); |
| } else { |
| setType(path.getType()); |
| } |
| } |
| context.usedVariable(name); |
| if (context.isDeclaredInOuterScope(name)) { |
| context.registerOuterScopeVariable(name); |
| } |
| } |
| |
| public Expression generateBaseBuilderExpression(GenerationContext context) { |
| //create builder, and add it, and answer it |
| //BUG 3106877: Need to create builder using the actual class (if using parallel expressions) |
| return new ExpressionBuilder(this.resolveClass(context)); |
| } |
| |
| @Override |
| public Expression generateExpression(GenerationContext generationContext) { |
| Expression myExpression = null; |
| String name = getCanonicalVariableName(); |
| |
| //is there a cached Expression? |
| myExpression = generationContext.expressionFor(name); |
| if (myExpression != null) { |
| return myExpression; |
| } |
| |
| //Either I have an alias type, or I'm an IN declaration |
| if (classConstant != null){ |
| myExpression = new ConstantExpression(classConstant, generationContext.getBaseExpression()); |
| } else if (generationContext.getParseTreeContext().isRangeVariable(name)) { |
| myExpression = generateBaseBuilderExpression(generationContext); |
| } else { |
| myExpression = generateExpressionForAlias(generationContext); |
| } |
| |
| generationContext.addExpression(myExpression, name); |
| return myExpression; |
| } |
| |
| public Expression generateExpressionForAlias(GenerationContext context) { |
| // BUG 3105651: Verify if we need to resolve this alias, or just use |
| // an empty ExpressionBuilder. See OrderByItemNode.generateExpression() |
| // for more details |
| if (context.getParseTree().getQueryNode().isSelectNode() && context.shouldCheckSelectNodeBeforeResolving() && (((SelectNode)context.getParseTree().getQueryNode()).isSelected(this.getCanonicalVariableName()))) { |
| return new ExpressionBuilder(); |
| } |
| |
| Node nodeForAlias = getNodeForAlias(context); |
| |
| //assume that if there is no node available for the given variable, then |
| //there must be an alias mismatch. Assume they know their attribute names better |
| //than their alias names. - JGL |
| if (nodeForAlias == null) { |
| throw JPQLException.aliasResolutionException( |
| context.getParseTreeContext().getQueryInfo(), |
| getLine(), getColumn(), getVariableName()); |
| } |
| |
| //create builder, and answer it |
| return nodeForAlias.generateExpression(context); |
| } |
| |
| public Node getNodeForAlias(GenerationContext context) { |
| //Node node = context.getParseTreeContext().nodeForIdentifier(getCanonicalVariableName()); |
| //return node != null ? ((IdentificationVariableDeclNode)node).getPath() : null; |
| return context.getParseTreeContext().pathForVariable(getCanonicalVariableName()); |
| } |
| |
| /** |
| * isAlias: Answer true if this variable represents an alias in the FROM clause. |
| * i.e. "FROM Employee emp" declares "emp" as an alias |
| */ |
| public boolean isAlias(GenerationContext context) { |
| return isAlias(context.getParseTreeContext()); |
| } |
| |
| public boolean isAlias(ParseTreeContext context) { |
| String classNameForAlias = context.schemaForVariable(getCanonicalVariableName()); |
| return classNameForAlias != null; |
| } |
| |
| /** |
| * resolveClass: Answer the class which corresponds to my variableName. This is the class for |
| * an alias, where the variableName is registered to an alias. |
| */ |
| @Override |
| public Class<?> resolveClass(GenerationContext generationContext) { |
| Class<?> clazz = null; |
| String name = getCanonicalVariableName(); |
| ParseTreeContext context = generationContext.getParseTreeContext(); |
| if (context.isRangeVariable(name)) { |
| String schema = context.schemaForVariable(name); |
| clazz = context.classForSchemaName(schema, generationContext); |
| } else { |
| DotNode path = (DotNode)context.pathForVariable(name); |
| if (path == null) { |
| throw JPQLException.aliasResolutionException( |
| context.getQueryInfo(), getLine(), getColumn(), name); |
| } else { |
| clazz = path.resolveClass(generationContext); |
| } |
| } |
| return clazz; |
| } |
| |
| @Override |
| public String toString(int indent) { |
| StringBuilder buffer = new StringBuilder(); |
| toStringIndent(indent, buffer); |
| buffer.append(toStringDisplayName()).append("[").append(getVariableName()).append("]"); |
| return buffer.toString(); |
| } |
| |
| /** |
| * INTERNAL |
| * Get the string representation of this node. |
| */ |
| @Override |
| public String getAsString() { |
| return getVariableName(); |
| } |
| |
| public Object getTypeForMapKey(ParseTreeContext context){ |
| String name = getCanonicalVariableName(); |
| if (context.isRangeVariable(name)) { |
| throw JPQLException.variableCannotHaveMapKey(context.getQueryInfo(), getLine(), getColumn(), name); |
| } else { |
| DotNode path = (DotNode)context.pathForVariable(name); |
| if (path == null) { |
| throw JPQLException.aliasResolutionException( |
| context.getQueryInfo(), getLine(), getColumn(), name); |
| } else { |
| return path.getTypeForMapKey(context); |
| } |
| } |
| } |
| |
| |
| @Override |
| public boolean isAliasableNode(){ |
| return true; |
| } |
| } |