org.eclipse.persistence.internal.jpa.parsing.ParseTree Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of eclipselink Show documentation
Show all versions of eclipselink Show documentation
EclipseLink build based upon Git transaction f2b9fc5
/*
* 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
* Purpose: A ParseTree contains Node(s). This contains a root Node and provides traversal utilities.
*
Responsibilities:
* - Add parameters to the query
*
- Generate an expression for the query
*
- Answer true if the tree has parameters
*
- Maintain the primary class name for the query
*
- Maintain the root of the parse tree
*
- Maintain the context for the parse tree
*
- Maintain the distinct state for the parse tree
*
- Print the contents of the parse tree on a string
*
* @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 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 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 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;
}
}