| /* |
| * 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 org.eclipse.persistence.expressions.Expression; |
| import org.eclipse.persistence.expressions.ExpressionBuilder; |
| import org.eclipse.persistence.internal.localization.ToStringLocalization; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.queries.DatabaseQuery; |
| import org.eclipse.persistence.queries.DeleteAllQuery; |
| import org.eclipse.persistence.queries.ModifyAllQuery; |
| import org.eclipse.persistence.queries.ObjectLevelReadQuery; |
| import org.eclipse.persistence.queries.UpdateAllQuery; |
| |
| import java.util.Iterator; |
| import java.util.Set; |
| |
| /** |
| * INTERNAL |
| * <p><b>Purpose</b>: A ParseTree contains Node(s). This contains a root Node and provides traversal utilities. |
| * <p><b>Responsibilities</b>:<ul> |
| * <li> Add parameters to the query |
| * <li> Generate an expression for the query |
| * <li> Answer true if the tree has parameters |
| * <li> Maintain the primary class name for the query |
| * <li> Maintain the root of the parse tree |
| * <li> Maintain the context for the parse tree |
| * <li> Maintain the distinct state for the parse tree |
| * <li> Print the contents of the parse tree on a string |
| * </ul> |
| * @author Jon Driscoll and Joel Lucuik |
| * @since TopLink 4.0 |
| */ |
| public class ParseTree { |
| private ParseTreeContext context; |
| private QueryNode queryNode; |
| private FromNode fromNode; |
| private SetNode setNode; |
| private WhereNode whereNode; |
| private OrderByNode orderByNode = null; |
| private GroupByNode groupByNode = null; |
| private HavingNode havingNode = null; |
| private ClassLoader classLoader = null; |
| private short distinctState = ObjectLevelReadQuery.UNCOMPUTED_DISTINCT; |
| private boolean validated = false; |
| private Set<String> unusedVariables = null; |
| |
| /** |
| * Return a new ParseTree. |
| */ |
| public ParseTree() { |
| super(); |
| } |
| |
| /** |
| * INTERNAL |
| * Returns a DatabaseQuery instance for this ParseTree. |
| */ |
| public DatabaseQuery createDatabaseQuery() { |
| return (queryNode == null) ? null : |
| queryNode.createDatabaseQuery(context); |
| } |
| |
| /** |
| * INTERNAL |
| * Adjust the reference class of the passed query if necessary |
| * |
| * Need to test this for Employee, employee.getAddress(), report query |
| */ |
| public void adjustReferenceClassForQuery(DatabaseQuery theQuery, GenerationContext generationContext) { |
| Class<?> referenceClass = getReferenceClass(theQuery, generationContext); |
| if ((referenceClass != null) && (referenceClass != theQuery.getReferenceClass())) { |
| if (theQuery.isObjectLevelReadQuery()) { |
| // The referenceClass needs to be changed. |
| // This should only happen in an ejbSelect... |
| ((ObjectLevelReadQuery)theQuery).setReferenceClass(referenceClass); |
| generationContext.setBaseQueryClass(referenceClass); |
| ((ObjectLevelReadQuery)theQuery).changeDescriptor(generationContext.getSession()); |
| } else if (theQuery.isUpdateAllQuery()) { |
| ((UpdateAllQuery)theQuery).setReferenceClass(referenceClass); |
| } else if (theQuery.isDeleteAllQuery()) { |
| ((DeleteAllQuery)theQuery).setReferenceClass(referenceClass); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL |
| * Initialize the base expression in the generation context. |
| */ |
| public void initBaseExpression(ObjectLevelReadQuery theQuery, GenerationContext generationContext) { |
| String variable = getFromNode().getFirstVariable(); |
| ParseTreeContext context = generationContext.getParseTreeContext(); |
| if (context.isRangeVariable(variable)) { |
| Class<?> referenceClass = theQuery.getReferenceClass(); |
| // Create a new expression builder for the reference class |
| ExpressionBuilder builder = new ExpressionBuilder(referenceClass); |
| // Use the expression builder as the default expression builder for the query |
| theQuery.setExpressionBuilder(builder); |
| // Add the expression builder to the expression cache in the context |
| generationContext.setBaseExpression(variable, builder); |
| } else { |
| // Get the declaring node for the variable |
| Node path = context.pathForVariable(variable); |
| // Get the ExpressionBuilder of the range variable for the path |
| Class<?> baseClass = getBaseExpressionClass(path, generationContext); |
| // and change the reference class accordingly |
| theQuery.setReferenceClass(baseClass); |
| theQuery.changeDescriptor(generationContext.getSession()); |
| generationContext.setBaseQueryClass(baseClass); |
| // Set the node expression as base expression |
| Expression baseExpression = path.generateExpression(generationContext); |
| generationContext.setBaseExpression(variable, baseExpression); |
| // Use the base ExpressionBuilder as the default for the query |
| theQuery.setExpressionBuilder(baseExpression.getBuilder()); |
| } |
| } |
| |
| /** |
| * INTERNAL |
| * Initialize the base expression in the generation context. |
| */ |
| public void initBaseExpression(ModifyAllQuery theQuery, GenerationContext generationContext) { |
| ModifyNode queryNode = (ModifyNode)getQueryNode(); |
| String variable = queryNode.getCanonicalAbstractSchemaIdentifier(); |
| Class<?> referenceClass = theQuery.getReferenceClass(); |
| // Create a new expression builder for the reference class |
| ExpressionBuilder builder = new ExpressionBuilder(referenceClass); |
| // Use the expression builder as the default expression builder for the query |
| theQuery.setExpressionBuilder(builder); |
| // Add the expression builder to the expression cache in the context |
| generationContext.setBaseExpression(variable, builder); |
| } |
| |
| /** */ |
| private Class<?> getBaseExpressionClass(Node node, GenerationContext generationContext) { |
| ParseTreeContext context = generationContext.getParseTreeContext(); |
| Class<?> clazz = null; |
| if (node != null) { |
| if (node.isDotNode()) { |
| // DotNode: delegate to left |
| clazz = getBaseExpressionClass(node.getLeft(), generationContext); |
| } else if (node.isVariableNode()) { |
| // VariableNode |
| String variable = ((VariableNode) node).getCanonicalVariableName(); |
| if (!context.isRangeVariable(variable)) { |
| Node path = context.pathForVariable(variable); |
| // Variable is defined in JOIN/IN clause => |
| // return the Class from its definition |
| clazz = getBaseExpressionClass(path, generationContext); |
| } else { |
| // Variable is defined in range variable decl => |
| // return its class |
| String schema = context.schemaForVariable(variable); |
| if (schema != null) { |
| clazz = context.classForSchemaName(schema, generationContext); |
| } |
| } |
| } |
| } |
| return clazz; |
| } |
| |
| /** |
| * INTERNAL |
| * Validate the parse tree. |
| */ |
| protected void validate(AbstractSession session, ClassLoader classLoader) { |
| validate(new TypeHelperImpl(session, classLoader)); |
| } |
| |
| /** |
| * INTERNAL |
| * Validate the parse tree. |
| */ |
| public void validate(TypeHelper typeHelper) { |
| ParseTreeContext context = getContext(); |
| context.setTypeHelper(typeHelper); |
| validate(context); |
| } |
| |
| /** |
| * INTERNAL |
| * Validate the parse tree. |
| */ |
| public void validate(ParseTreeContext context) { |
| if (validated) { |
| // already validated => return |
| return; |
| } |
| |
| validated = true; |
| context.enterScope(); |
| if (fromNode != null) { |
| fromNode.validate(context); |
| } |
| queryNode.validate(context); |
| qualifyAttributeAccess(context); |
| if (setNode != null) { |
| setNode.validate(context); |
| } |
| if (whereNode != null) { |
| whereNode.validate(context); |
| } |
| if (hasOrderBy()) { |
| orderByNode.validate(context, (SelectNode)queryNode); |
| } |
| if (hasGroupBy()) { |
| groupByNode.validate(context, (SelectNode)queryNode); |
| } |
| if (hasHaving()) { |
| havingNode.validate(context, groupByNode); |
| } |
| // store the set od unused variable for later use |
| unusedVariables = context.getUnusedVariables(); |
| context.leaveScope(); |
| } |
| |
| /** |
| * INTERNAL |
| * This method handles any unqualified field access in bulk UPDATE and |
| * DELETE statements. A UPDATE or DELETE statement may not define an |
| * identification variable. In this case any field accessed from the |
| * current class is not qualified with an identification variable, e.g. |
| * UPDATE Customer SET name = :newname |
| * The method goes through the expressions of the SET clause and the WHERE |
| * clause of such an DELETE and UPDATE statement and qualifies the field |
| * access using the abstract schema name as qualifier. |
| */ |
| protected void qualifyAttributeAccess(ParseTreeContext context) { |
| if ((queryNode.isUpdateNode() || queryNode.isDeleteNode()) && |
| ((ModifyNode)queryNode).getAbstractSchemaIdentifier() == null) { |
| if (setNode != null) { |
| setNode.qualifyAttributeAccess(context); |
| } |
| if (whereNode != null) { |
| whereNode.qualifyAttributeAccess(context); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL |
| * Add the ordering to the passed query |
| */ |
| public void addOrderingToQuery(ObjectLevelReadQuery theQuery, GenerationContext generationContext) { |
| if (hasOrderBy()) { |
| (getOrderByNode()).addOrderingToQuery(theQuery, generationContext); |
| } |
| } |
| |
| /** |
| * INTERNAL |
| * Add the grouping to the passed query |
| */ |
| public void addGroupingToQuery(ObjectLevelReadQuery theQuery, GenerationContext generationContext) { |
| if (hasGroupBy()) { |
| (getGroupByNode()).addGroupingToQuery(theQuery, generationContext); |
| } |
| } |
| |
| /** |
| * INTERNAL |
| * Add the having to the passed query |
| */ |
| public void addHavingToQuery(ObjectLevelReadQuery theQuery, GenerationContext generationContext) { |
| if (hasHaving()) { |
| (getHavingNode()).addHavingToQuery(theQuery, generationContext); |
| } |
| } |
| |
| /** |
| * INTERNAL |
| */ |
| public void addNonFetchJoinAttributes(ObjectLevelReadQuery theQuery, GenerationContext generationContext) { |
| ParseTreeContext context = generationContext.getParseTreeContext(); |
| for (Iterator<String> i = unusedVariables.iterator(); i.hasNext();) { |
| String variable = i.next(); |
| Expression expr = null; |
| if (!context.isRangeVariable(variable)) { |
| Node path = context.pathForVariable(variable); |
| expr = path.generateExpression(generationContext); |
| theQuery.addNonFetchJoinedAttribute(expr); |
| } else { |
| // Ignore, assume 'Select 1 from Employee e' or sorts |
| // unused range variable => not supported yet |
| //throw JPQLException.notYetImplemented(context.getQueryInfo(), |
| // "Variable [" + variable + "] is defined in a range variable declaration, but not used in the rest of the query."); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL |
| * Add the updates to the passed query |
| */ |
| public void addUpdatesToQuery(UpdateAllQuery theQuery, GenerationContext generationContext) { |
| if (getSetNode() != null) { |
| (getSetNode()).addUpdatesToQuery(theQuery, generationContext); |
| } |
| } |
| |
| /** |
| * INTERNAL |
| * Add parameters to the query |
| */ |
| public void addParametersToQuery(DatabaseQuery query) { |
| //Bug#4646580 Add arguments to query |
| if (context.hasParameters()) { |
| TypeHelper typeHelper = context.getTypeHelper(); |
| for (Iterator<String> i = context.getParameterNames().iterator(); i.hasNext();) { |
| String param = i.next(); |
| Object type = context.getParameterType(param); |
| Class<?> clazz = typeHelper.getJavaClass(type); |
| if (clazz == null) { |
| clazz = Object.class; |
| } |
| query.addArgument(param, clazz); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL |
| * Apply the select or update to the passed query. |
| * If there is a single attribute being selected, add it to the query result set |
| * If an aggregate is being used, add it to the query result set |
| */ |
| public void applyQueryNodeToQuery(DatabaseQuery theQuery, GenerationContext generationContext) { |
| getQueryNode().applyToQuery(theQuery, generationContext); |
| } |
| |
| /** |
| * INTERNAL |
| * Build the context to be used when generating the expression from the parse tree |
| */ |
| public GenerationContext buildContext(DatabaseQuery query, AbstractSession sessionForContext) { |
| if (query.isObjectLevelReadQuery()) { |
| return buildContextForReadQuery(sessionForContext); |
| } else if (query.isUpdateAllQuery() || query.isDeleteAllQuery()) { |
| return new GenerationContext(getContext(), sessionForContext, this); |
| } |
| return null; |
| } |
| |
| /** |
| * INTERNAL |
| * Build the context to be used when generating the expression from the parse tree |
| */ |
| public GenerationContext buildContextForReadQuery(AbstractSession sessionForContext) { |
| return new SelectGenerationContext(getContext(), sessionForContext, this); |
| } |
| |
| /** |
| * INTERNAL |
| * Build a context for the expression generation |
| */ |
| public Expression generateExpression(DatabaseQuery readQuery, GenerationContext generationContext) { |
| Expression selectExpression = getQueryNode().generateExpression(generationContext); |
| if (getWhereNode() == null) { |
| return selectExpression; |
| } |
| Expression whereExpression = getWhereNode().generateExpression(generationContext); |
| |
| selectExpression = getQueryNode().generateExpression(generationContext); |
| if (selectExpression != null) { |
| whereExpression = selectExpression.and(whereExpression); |
| } |
| return whereExpression; |
| } |
| |
| /** |
| * Return the context for this parse tree |
| */ |
| public ParseTreeContext getContext() { |
| return context; |
| } |
| |
| /** |
| * INTERNAL |
| * Return the FROM Node |
| */ |
| public FromNode getFromNode() { |
| return fromNode; |
| } |
| |
| /** |
| * INTERNAL |
| * Return a class loader |
| * @return java.lang.ClassLoader |
| */ |
| public ClassLoader getClassLoader() { |
| if (classLoader == null) { |
| return org.eclipse.persistence.internal.helper.ConversionManager.getDefaultManager().getLoader(); |
| } else { |
| return classLoader; |
| } |
| } |
| |
| /** |
| * INTERNAL |
| * Return the OrderByNode |
| */ |
| public OrderByNode getOrderByNode() { |
| return orderByNode; |
| } |
| |
| /** |
| * INTERNAL |
| * Return the GroupByNode |
| */ |
| public GroupByNode getGroupByNode() { |
| return groupByNode; |
| } |
| |
| /** |
| * INTERNAL |
| * Return the HavingNode |
| */ |
| public HavingNode getHavingNode() { |
| return havingNode; |
| } |
| |
| /** |
| * getReferenceClass(): Answer the class which will be the reference class for the query. |
| * Resolve this using the node parsed from the "SELECT" of the EJBQL query string |
| */ |
| public Class<?> getReferenceClass(DatabaseQuery query, GenerationContext generationContext) { |
| if (getQueryNode() == null) { |
| return null; |
| } |
| return getQueryNode().getReferenceClass(generationContext); |
| } |
| |
| /** |
| * INTERNAL |
| * Return the root node for the tree |
| */ |
| public QueryNode getQueryNode() { |
| return queryNode; |
| } |
| |
| /** |
| * INTERNAL |
| * Return the set node for the tree |
| */ |
| public SetNode getSetNode() { |
| return setNode; |
| } |
| |
| /** |
| * INTERNAL |
| * Return the Where node |
| */ |
| public WhereNode getWhereNode() { |
| return whereNode; |
| } |
| |
| /** |
| * INTERNAL |
| * Return the DISTINCT state for the tree |
| */ |
| public short getDistinctState() { |
| return distinctState; |
| } |
| |
| /** |
| * INTERNAL |
| * Does this EJBQL have an Ordering Clause |
| */ |
| public boolean hasOrderBy() { |
| return getOrderByNode() != null; |
| } |
| |
| /** |
| * INTERNAL |
| * Does this EJBQL have a Grouping Clause |
| */ |
| public boolean hasGroupBy() { |
| return getGroupByNode() != null; |
| } |
| |
| /** |
| * INTERNAL |
| * Does this EJBQL have a Having Clause |
| */ |
| public boolean hasHaving() { |
| return getHavingNode() != null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the class loader for this parse tree |
| */ |
| public void setClassLoader(ClassLoader loader){ |
| this.classLoader = loader; |
| } |
| |
| /** |
| * INTERNAL |
| * Set the context for this parse tree |
| */ |
| public void setContext(ParseTreeContext newContext) { |
| context = newContext; |
| } |
| |
| /** |
| * INTERNAL |
| * Set the FROM node for the query |
| */ |
| public void setFromNode(FromNode fromNode) { |
| this.fromNode = fromNode; |
| } |
| |
| /** |
| * INTERNAL |
| * Set the Order by node |
| */ |
| public void setOrderByNode(OrderByNode newOrderByNode) { |
| orderByNode = newOrderByNode; |
| } |
| |
| /** |
| * INTERNAL |
| * Set the Group by node |
| */ |
| public void setGroupByNode(GroupByNode newGroupByNode) { |
| groupByNode = newGroupByNode; |
| } |
| |
| /** |
| * INTERNAL |
| * Set the Having node |
| */ |
| public void setHavingNode(HavingNode newHavingNode) { |
| havingNode = newHavingNode; |
| } |
| |
| public void setSelectionCriteriaForQuery(DatabaseQuery theQuery, GenerationContext generationContext) { |
| theQuery.setSelectionCriteria(generateExpression(theQuery, generationContext)); |
| } |
| |
| /** |
| * INTERNAL |
| * Set the Select node |
| */ |
| public void setQueryNode(QueryNode newQueryNode) { |
| queryNode = newQueryNode; |
| } |
| |
| /** |
| * INTERNAL |
| * Set the Where node |
| */ |
| public void setSetNode(SetNode newSetNode) { |
| setNode = newSetNode; |
| } |
| |
| /** |
| * INTERNAL |
| * Set the Where node |
| */ |
| public void setWhereNode(WhereNode newWhereNode) { |
| whereNode = newWhereNode; |
| } |
| |
| /** |
| * INTERNAL |
| * Set the DISTINCT state for the tree |
| */ |
| public void setDistinctState(short newDistinctState) { |
| distinctState = newDistinctState; |
| } |
| |
| /** |
| * INTERNAL |
| * Print the contents of the parse tree on a string |
| */ |
| @Override |
| public String toString() { |
| return ToStringLocalization.buildMessage("context", null) + " " + getContext().toString(); |
| } |
| |
| /** |
| * INTERNAL |
| * Verify that the alias in the SELECT is valid. |
| * Invalid: SELECT OBJECT(badAlias) FROM Employee employee.... |
| * Valid: SELECT OBJECT(employee) FROM Employee employee.... |
| */ |
| public void verifySelect(DatabaseQuery theQuery, GenerationContext generationContext) { |
| if (theQuery.isObjectLevelReadQuery()) { |
| //verify the selected alias, |
| //this will throw an error if the alias is bad |
| ((SelectNode)getQueryNode()).verifySelectedAlias(generationContext); |
| } |
| } |
| |
| /** |
| * INTERNAL |
| * Answer true if DISTINCT has been chosen. |
| */ |
| public boolean usesDistinct() { |
| return distinctState == ObjectLevelReadQuery.USE_DISTINCT; |
| } |
| } |