| /* |
| * 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; |
| |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.expressions.Expression; |
| import org.eclipse.persistence.queries.DatabaseQuery; |
| import org.eclipse.persistence.queries.ObjectLevelReadQuery; |
| import org.eclipse.persistence.queries.ReadAllQuery; |
| import org.eclipse.persistence.queries.ReportQuery; |
| |
| /** |
| * INTERNAL: |
| * <p><b>Purpose</b>: Represent a SELECT |
| * <p><b>Responsibilities</b>:<ul> |
| * <li> Hold the distinct status |
| * <li> Modify a query based on the contents |
| * </ul> |
| * |
| * <p>The SELECT statement determines the return type of an EJBQL query. |
| * The SELECT may also determine the distinct state of a query |
| * |
| * A SELECT can be one of the following: |
| * <pre> |
| * 1. SELECT OBJECT(someObject)... This query will return a collection of objects |
| * 2. SELECT anObject.anAttribute ... This will return a collection of anAttribute |
| * 3. SELECT <aggregateFunction> ... This will return a single value |
| * The allowable aggregateFunctions are: AVG, COUNT, MAX, MIN, SUM |
| * SELECT AVG(emp.salary)... Returns the average of all the employees salaries |
| * SELECT COUNT(emp)... Returns a count of the employees |
| * SELECT COUNT(emp.firstName)... Returns a count of the employee's firstNames |
| * SELECT MAX(emp.salary)... Returns the maximum employee salary |
| * SELECT MIN(emp.salary)... Returns the minimum employee salary |
| * SELECT SUM(emp.salary)... Returns the sum of all the employees salaries |
| * </pre> |
| * @author Jon Driscoll |
| * @since TopLink 5.0 |
| */ |
| public class SelectNode extends QueryNode { |
| |
| private List<Node> selectExpressions = new ArrayList<>(); |
| private List<String> identifiers = new ArrayList<>(); |
| |
| private boolean distinct = false; |
| |
| public SelectNode() { |
| } |
| |
| public List<Node> getSelectExpressions() { |
| return selectExpressions; |
| } |
| |
| public void setSelectExpressions(List<Node> exprs) { |
| selectExpressions = exprs; |
| } |
| |
| public List<String> getIdentifiers() { |
| return identifiers; |
| } |
| |
| public void setIdentifiers(List<String> identifiers) { |
| this.identifiers = identifiers; |
| } |
| |
| public boolean usesDistinct() { |
| return distinct; |
| } |
| public void setDistinct(boolean distinct) { |
| this.distinct = distinct; |
| } |
| |
| /** |
| * Returns a DatabaseQuery instance representing the owning |
| * ParseTree. This implementation returns a ReadAllQuery for simple SELECT |
| * queries and a ReportQuery otherwise. |
| */ |
| @Override |
| public DatabaseQuery createDatabaseQuery(ParseTreeContext context) { |
| |
| // TODO: This optimization needs to be revisited because it causes GlassFish issues: 2084 and 2171 |
| // These issues have been solve in GlassFish by always generating a ReportQuery |
| // The same fix has not been made in Oracle TopLink because it disables some advanced JPA Query Hints |
| ObjectLevelReadQuery query; |
| if (isReadAllQuery(context)) { |
| query = new ReadAllQuery(); |
| } else { |
| query = new ReportQuery(); |
| } |
| query.dontUseDistinct(); //gf bug 1395- prevents using distinct unless user specified |
| return query; |
| } |
| |
| /** |
| * Returns true if the SELECT clause consists of a single expression |
| * returning the base identification variable of the query and if the base |
| * variable is defined as a range variable w/o FETCH JOINs. |
| */ |
| private boolean isReadAllQuery(ParseTreeContext context) { |
| if (!isSingleSelectExpression()) { |
| // multiple expressions in the select clause => ReportQuery |
| return false; |
| } |
| |
| Node node = getFirstSelectExpressionNode(); |
| if (!node.isVariableNode()) { |
| // Does not select an identification variable (e.g. projection or |
| // aggregate function) => ReportQuery |
| return false; |
| } |
| String variable = ((VariableNode)node).getCanonicalVariableName(); |
| |
| // Note, the base variable in ParseTreeContext is not yet set => |
| // calculate it |
| String baseVariable = getParseTree().getFromNode().getFirstVariable(); |
| if (!context.isRangeVariable(baseVariable)) { |
| // Query's base variable is not a range variable. |
| return false; |
| } |
| |
| // Bug 393470 |
| // Use ReportQuery for GROUP BY / HAVING clauses in ANTLR |
| if (getParseTree().hasGroupBy() || getParseTree().hasHaving()) { |
| return false; |
| } |
| |
| // Use ReadAllQuery if the variable of the SELECT clause expression is |
| // the base variable |
| return baseVariable.equals(variable); |
| } |
| |
| /** |
| * INTERNAL |
| * Apply this node to the passed query |
| */ |
| @Override |
| public void applyToQuery(DatabaseQuery theQuery, GenerationContext context) { |
| ObjectLevelReadQuery readQuery = (ObjectLevelReadQuery)theQuery; |
| if (selectExpressions.isEmpty()) { |
| return; |
| } |
| |
| //set the distinct state |
| //BUG 3168673: Don't set distinct state if we're using Count |
| if (!(isSingleSelectExpression() && getFirstSelectExpressionNode().isCountNode())) { |
| // Set the distinct state for the query |
| if (usesDistinct()) { |
| getParseTree().setDistinctState(ObjectLevelReadQuery.USE_DISTINCT); |
| readQuery.setDistinctState(ObjectLevelReadQuery.USE_DISTINCT); |
| } |
| } |
| |
| if (readQuery instanceof ReportQuery) { |
| ReportQuery reportQuery = (ReportQuery)readQuery; |
| reportQuery.returnWithoutReportQueryResult(); |
| if (isSingleSelectExpression()) { |
| reportQuery.returnSingleAttribute(); |
| } |
| } |
| SelectGenerationContext selectContext = (SelectGenerationContext)context; |
| for (int i=0;i<selectExpressions.size();i++){ |
| Node node = selectExpressions.get(i); |
| if (selectingRelationshipField(node, context)) { |
| selectContext.useOuterJoins(); |
| } |
| if (node.isAliasableNode() && identifiers != null){ |
| String alias = identifiers.get(i); |
| if (alias != null){ |
| ((AliasableNode)node).setAlias(alias); |
| } |
| } |
| node.applyToQuery(readQuery, context); |
| selectContext.dontUseOuterJoins(); |
| } |
| |
| //indicate on the query if "return null if primary key null" |
| //This means we want nulls returned if we expect an outer join |
| readQuery.setShouldBuildNullForNullPk(this.hasOneToOneSelected(context)); |
| |
| } |
| |
| public boolean hasOneToOneSelected(GenerationContext context) { |
| // Iterate the select expression and return true if one of it has a |
| // oneToOne selected. |
| for (Iterator<Node> i = selectExpressions.iterator(); i.hasNext();) { |
| Node node = i.next(); |
| if (hasOneToOneSelected(node, context)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Answer true if there is a one-to-one relationship selected. |
| * This includes a chain of relationships. |
| * True: SELECT employee.address FROM ..... //Simple 1:1 |
| * True: SELECT a.b.c.d FROM ..... //where a-{@literal >}b, b-{@literal >}c and c-{@literal >}d are all 1:1. |
| * False: SELECT OBJECT(employee) FROM ..... //simple SELECT |
| * False: SELECT phoneNumber.areaCode FROM ..... //direct-to-field |
| */ |
| private boolean hasOneToOneSelected(Node node, GenerationContext context) { |
| //BUG 3240484: Not SELECTing 1:1 if it's in a COUNT |
| if (node.isCountNode()) { |
| return false; |
| } |
| |
| if (node.isAggregateNode()) { |
| // delegate to aggregate expression |
| return hasOneToOneSelected(node.getLeft(), context); |
| } |
| |
| if (node.isVariableNode()){ |
| return !nodeRefersToObject(node, context); |
| } |
| |
| if (node.isConstructorNode()) { |
| List<Node> args = ((ConstructorNode)node).getConstructorItems(); |
| for (Iterator<Node> i = args.iterator(); i.hasNext();) { |
| Node arg = i.next(); |
| if (hasOneToOneSelected(arg, context)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // check whether it is a direct-to-field mapping |
| return !selectingDirectToField(node, context); |
| } |
| |
| /** |
| * Verify that the selected alias is a valid alias. If it's not valid, |
| * an Exception will be thrown, likely JPQLException.aliasResolutionException. |
| * |
| * Valid: SELECT OBJECT(emp) FROM Employee emp WHERE ... |
| * Invalid: SELECT OBJECT(badAlias) FROM Employee emp WHERE ... |
| */ |
| public void verifySelectedAlias(GenerationContext context) { |
| for (Iterator<Node> i = selectExpressions.iterator(); i.hasNext();) { |
| Node node = i.next(); |
| //if the node is a DotNode, there is no selected alias |
| if (node.isDotNode()) { |
| return; |
| } |
| node.resolveClass(context); |
| } |
| } |
| |
| /** |
| * Answer true if the variable name given as argument is SELECTed. |
| * |
| * True: "SELECT OBJECT(emp) ...." & variableName = "emp" |
| * False: "SELECT OBJECT(somethingElse) ..." & variableName = "emp" |
| */ |
| public boolean isSelected(String variableName) { |
| for (Iterator<Node> i = selectExpressions.iterator(); i.hasNext();) { |
| Node node = i.next(); |
| //Make sure we've SELECted a VariableNode |
| if (node.isVariableNode() && |
| ((VariableNode)node).getCanonicalVariableName().equals(variableName)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean isSelectNode() { |
| return true; |
| } |
| |
| /** |
| * Check the select expression nodes for a path expression starting with a |
| * unqualified field access and if so, replace it by a qualified field |
| * access. |
| */ |
| @Override |
| public Node qualifyAttributeAccess(ParseTreeContext context) { |
| for (int i = 0; i < selectExpressions.size(); i++) { |
| Node item = selectExpressions.get(i); |
| selectExpressions.set(i, item.qualifyAttributeAccess(context)); |
| } |
| return this; |
| } |
| |
| /** |
| * Validate node. |
| */ |
| @Override |
| public void validate(ParseTreeContext context) { |
| for (Iterator<Node> i = selectExpressions.iterator(); i.hasNext(); ) { |
| Node item = i.next(); |
| item.validate(context); |
| } |
| } |
| |
| /** |
| * Answer the class associated with my left node. |
| */ |
| @Override |
| public Class<?> resolveClass(GenerationContext context) { |
| return getReferenceClass(context); |
| } |
| |
| /** |
| * Return a EclipseLink expression generated using the left node. |
| */ |
| @Override |
| public Expression generateExpression(GenerationContext context) { |
| return null; |
| } |
| |
| /** |
| * Compute the Reference class for this query. |
| * @return the class this query is querying for |
| */ |
| @Override |
| public Class<?> getReferenceClass(GenerationContext context) { |
| return getClassOfFirstVariable(context); |
| } |
| |
| private Class<?> getClassOfFirstVariable(GenerationContext context) { |
| Class<?> clazz = null; |
| String variable = getParseTree().getFromNode().getFirstVariable(); |
| ParseTreeContext parseTreeContext = context.getParseTreeContext(); |
| if (parseTreeContext.isRangeVariable(variable)) { |
| String schema = parseTreeContext.schemaForVariable(variable); |
| // variables is defines in a range variable declaration, so there |
| // is a schema name for this variable |
| clazz = parseTreeContext.classForSchemaName(schema, context); |
| } else { |
| // variable is defined in a JOIN clause, so there is a a defining |
| // node for the variable |
| Node path = parseTreeContext.pathForVariable(variable); |
| clazz = path.resolveClass(context); |
| } |
| return clazz; |
| } |
| |
| /** |
| * Answer true if a variable in the IN clause is SELECTed |
| */ |
| public boolean isVariableInINClauseSelected(GenerationContext context) { |
| for (Iterator<Node> i = selectExpressions.iterator(); i.hasNext();) { |
| Node node = i.next(); |
| |
| if (node.isVariableNode()) { |
| String variableNameForLeft = ((VariableNode)node).getCanonicalVariableName(); |
| if (!context.getParseTreeContext().isRangeVariable(variableNameForLeft)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Answer true if this node refers to an object described later in the EJBQL |
| * True: SELECT p FROM Project p |
| * False: SELECT p.id FROM Project p |
| */ |
| public boolean nodeRefersToObject(Node node, GenerationContext context) { |
| if (!node.isVariableNode()){ |
| return false; |
| } |
| String name = ((VariableNode)node).getCanonicalVariableName(); |
| String alias = context.getParseTreeContext().schemaForVariable(name); |
| if (alias != null){ |
| ClassDescriptor descriptor = context.getSession().getDescriptorForAlias(alias); |
| if (descriptor != null){ |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private boolean selectingRelationshipField(Node node, GenerationContext context) { |
| if ((node == null) || !node.isDotNode()) { |
| return false; |
| } |
| TypeHelper typeHelper = context.getParseTreeContext().getTypeHelper(); |
| Node path = node.getLeft(); |
| AttributeNode attribute = (AttributeNode)node.getRight(); |
| return typeHelper.isRelationship(path.getType(), |
| attribute.getAttributeName()); |
| } |
| |
| /** |
| * Answer true if the SELECT ends in a direct-to-field. |
| * true: SELECT phone.areaCode |
| * false: SELECT employee.address |
| */ |
| private boolean selectingDirectToField(Node node, GenerationContext context) { |
| |
| if ((node == null) || !node.isDotNode()) { |
| return false; |
| } |
| return ((DotNode)node).endsWithDirectToField(context); |
| } |
| |
| /** |
| * Returns the first select expression node. |
| */ |
| private Node getFirstSelectExpressionNode() { |
| return selectExpressions.size() > 0 ? |
| selectExpressions.get(0) : null; |
| } |
| |
| private boolean isSingleSelectExpression() { |
| return selectExpressions.size() == 1; |
| } |
| |
| } |