| /* |
| * Copyright (c) 2006, 2021 Oracle and/or its affiliates. All rights reserved. |
| * Copyright (c) 2019 IBM 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 |
| // |
| package org.eclipse.persistence.internal.jpa.jpql; |
| |
| import java.util.Collection; |
| import org.eclipse.persistence.expressions.Expression; |
| import org.eclipse.persistence.expressions.ExpressionBuilder; |
| import org.eclipse.persistence.history.AsOfSCNClause; |
| import org.eclipse.persistence.jpa.jpql.LiteralType; |
| import org.eclipse.persistence.jpa.jpql.parser.AbstractEclipseLinkExpressionVisitor; |
| import org.eclipse.persistence.jpa.jpql.parser.AbstractFromClause; |
| import org.eclipse.persistence.jpa.jpql.parser.AbstractSelectClause; |
| import org.eclipse.persistence.jpa.jpql.parser.AbstractSelectStatement; |
| import org.eclipse.persistence.jpa.jpql.parser.AsOfClause; |
| import org.eclipse.persistence.jpa.jpql.parser.AvgFunction; |
| import org.eclipse.persistence.jpa.jpql.parser.CollectionExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.CollectionMemberDeclaration; |
| import org.eclipse.persistence.jpa.jpql.parser.ConstructorExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.CountFunction; |
| import org.eclipse.persistence.jpa.jpql.parser.EclipseLinkAnonymousExpressionVisitor; |
| 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.Join; |
| import org.eclipse.persistence.jpa.jpql.parser.KeyExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.MaxFunction; |
| import org.eclipse.persistence.jpa.jpql.parser.MinFunction; |
| import org.eclipse.persistence.jpa.jpql.parser.ObjectExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.OrderByClause; |
| import org.eclipse.persistence.jpa.jpql.parser.OrderByItem; |
| 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.StateFieldPathExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.SumFunction; |
| import org.eclipse.persistence.jpa.jpql.parser.UnionClause; |
| import org.eclipse.persistence.jpa.jpql.parser.ValueExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.WhereClause; |
| import org.eclipse.persistence.mappings.DatabaseMapping; |
| import org.eclipse.persistence.queries.ObjectLevelReadQuery; |
| import org.eclipse.persistence.queries.ReportQuery; |
| |
| /** |
| * This visitor is responsible to populate an {@link ObjectLevelReadQuery} by traversing a {@link |
| * org.eclipse.persistence.jpa.jpql.parser.Expression JPQL Expression} representing a |
| * <b>SELECT</b> query. |
| * |
| * @see ObjectLevelReadQueryVisitor |
| * @see ReportQueryVisitor |
| * |
| * @version 2.5 |
| * @since 2.3 |
| * @author Pascal Filion |
| * @author John Bracken |
| */ |
| abstract class AbstractObjectLevelReadQueryVisitor extends AbstractEclipseLinkExpressionVisitor { |
| |
| /** |
| * The {@link ObjectLevelReadQuery} to populate. |
| */ |
| ObjectLevelReadQuery query; |
| |
| /** |
| * The {@link JPQLQueryContext} is used to query information about the application metadata and |
| * cached information. |
| */ |
| final JPQLQueryContext queryContext; |
| |
| /** |
| * Creates a new <code>ReadAllQueryVisitor</code>. |
| * |
| * @param queryContext The context used to query information about the application metadata and |
| * cached information |
| * @param query The {@link ObjectLevelReadQuery} to populate by using this visitor to visit the |
| * parsed tree representation of the JPQL query |
| */ |
| AbstractObjectLevelReadQueryVisitor(JPQLQueryContext queryContext, ObjectLevelReadQuery query) { |
| super(); |
| this.query = query; |
| this.queryContext = queryContext; |
| } |
| |
| @Override |
| public void visit(AsOfClause expression) { |
| |
| Expression queryExpression = queryContext.buildExpression(expression); |
| org.eclipse.persistence.history.AsOfClause asOfClause; |
| |
| if (expression.hasScn()) { |
| asOfClause = new AsOfSCNClause(queryExpression); |
| } |
| else { |
| asOfClause = new org.eclipse.persistence.history.AsOfClause(queryExpression); |
| } |
| |
| query.setAsOfClause(asOfClause); |
| query.setShouldMaintainCache(false); |
| } |
| |
| @Override |
| public void visit(CollectionExpression expression) { |
| expression.acceptChildren(this); |
| } |
| |
| @Override |
| public void visit(FromClause expression) { |
| visitAbstractFromClause(expression); |
| } |
| |
| @Override |
| public void visit(IdentificationVariable expression) { |
| visitIdentificationVariable(expression); |
| } |
| |
| @Override |
| public void visit(ObjectExpression expression) { |
| expression.getExpression().accept(this); |
| } |
| |
| @Override |
| public void visit(OrderByClause expression) { |
| expression.getOrderByItems().accept(this); |
| } |
| |
| @Override |
| public void visit(OrderByItem expression) { |
| Expression queryExpression = queryContext.buildExpression(expression); |
| query.addOrdering(queryExpression); |
| } |
| |
| @Override |
| public void visit(SelectClause expression) { |
| // Select couple flags |
| visitAbstractSelectClause(expression); |
| } |
| |
| @Override |
| public void visit(SelectStatement expression) { |
| |
| // Handle SELECT/FROM/WHERE clauses |
| visitAbstractSelectStatement(expression); |
| |
| // ORDER BY clause |
| if (expression.hasOrderByClause()) { |
| expression.getOrderByClause().accept(this); |
| } |
| |
| // UNION clauses |
| if (expression.hasUnionClauses()) { |
| expression.getUnionClauses().accept(this); |
| } |
| } |
| |
| @Override |
| public void visit(SimpleFromClause expression) { |
| visitAbstractFromClause(expression); |
| } |
| |
| @Override |
| public void visit(SimpleSelectClause expression) { |
| // Select couple flags |
| visitAbstractSelectClause(expression); |
| } |
| |
| @Override |
| public void visit(SimpleSelectStatement expression) { |
| // Handle SELECT/FROM/WHERE |
| visitAbstractSelectStatement(expression); |
| } |
| |
| @Override |
| public void visit(UnionClause expression) { |
| |
| ReportQuery subquery = queryContext.buildSubquery((SimpleSelectStatement) expression.getQuery()); |
| Expression union = null; |
| |
| if (expression.isUnion()) { |
| if (expression.hasAll()) { |
| union = query.getExpressionBuilder().unionAll(subquery); |
| } |
| else { |
| union = query.getExpressionBuilder().union(subquery); |
| } |
| } |
| else if (expression.isIntersect()) { |
| if (expression.hasAll()) { |
| union = query.getExpressionBuilder().intersectAll(subquery); |
| } |
| else { |
| union = query.getExpressionBuilder().intersect(subquery); |
| } |
| } |
| else if (expression.isExcept()) { |
| if (expression.hasAll()) { |
| union = query.getExpressionBuilder().exceptAll(subquery); |
| } |
| else { |
| union = query.getExpressionBuilder().except(subquery); |
| } |
| } |
| |
| query.addUnionExpression(union); |
| } |
| |
| @Override |
| public void visit(WhereClause expression) { |
| query.setSelectionCriteria(queryContext.buildExpression(expression)); |
| } |
| |
| void visitAbstractFromClause(AbstractFromClause expression) { |
| |
| // Set the ExpressionBuilder |
| Expression baseExpression = queryContext.getBaseExpression(); |
| ExpressionBuilder expressionBuilder = baseExpression.getBuilder(); |
| query.setExpressionBuilder(expressionBuilder); |
| |
| // Set the reference class if it's not set |
| if (query.getReferenceClass() == null) { |
| query.setReferenceClass(expressionBuilder.getQueryClass()); |
| query.changeDescriptor(queryContext.getSession()); |
| } |
| |
| // Add join expressions to the query (but not the join fetch expressions) |
| JoinVisitor visitor = new JoinVisitor(); |
| expression.accept(visitor); |
| |
| // Visit the AS OF clause |
| if (expression.hasAsOfClause()) { |
| expression.getAsOfClause().accept(this); |
| } |
| |
| // Visit the hierarchical clause |
| if (expression.hasHierarchicalQueryClause()) { |
| expression.getHierarchicalQueryClause().accept(this); |
| } |
| } |
| |
| void visitAbstractSelectClause(AbstractSelectClause expression) { |
| |
| // DISTINCT |
| if (expression.hasDistinct()) { |
| |
| CountFunctionVisitor visitor = new CountFunctionVisitor(); |
| expression.accept(visitor); |
| |
| if (!visitor.hasCountFunction) { |
| query.useDistinct(); |
| } |
| } |
| |
| // Indicate on the query if "return null if primary key null". |
| // This means we want nulls returned if we expect an outer join |
| // True: SELECT employee.address FROM ..... // Simple 1:1 |
| // True: SELECT a.b.c.d FROM ..... // where a->b, b->c and c->d are all 1:1. |
| // False: SELECT OBJECT(employee) FROM ..... // simple SELECT |
| // False: SELECT phoneNumber.areaCode FROM ..... // direct-to-field |
| OneToOneSelectedVisitor visitor = new OneToOneSelectedVisitor(); |
| expression.accept(visitor); |
| query.setShouldBuildNullForNullPk(visitor.oneToOneSelected); |
| |
| // Now visit the select expression |
| expression.getSelectExpression().accept(this); |
| } |
| |
| void visitAbstractSelectStatement(AbstractSelectStatement expression) { |
| |
| // First visit the FROM clause in order to retrieve the reference classes and |
| // create an ExpressionBuilder for each abstract schema name |
| expression.getFromClause().accept(this); |
| expression.getSelectClause().accept(this); |
| |
| if (expression.hasWhereClause()) { |
| expression.getWhereClause().accept(this); |
| } |
| } |
| |
| void visitIdentificationVariable(IdentificationVariable expression) { |
| |
| String variableName = expression.getVariableName(); |
| |
| // Retrieve the join fetches that were defined in the same identification variable |
| // declaration, if the identification variable is mapped to a join, then there will |
| // not be any join fetch associated with it |
| Collection<Join> joinFetches = queryContext.getJoinFetches(variableName); |
| |
| if (joinFetches != null ) { |
| |
| for (Join joinFetch : joinFetches) { |
| |
| |
| // Retrieve the join association path expression's identification variable |
| String joinFetchVariableName = queryContext.literal( |
| joinFetch, |
| LiteralType.PATH_EXPRESSION_IDENTIFICATION_VARIABLE |
| ); |
| |
| // Both identification variables are the same. |
| // Then add the join associated path expression as a joined attribute |
| // Example: FROM Employee e JOIN FETCH e.employees |
| if (variableName.equals(joinFetchVariableName)) { |
| org.eclipse.persistence.expressions.Expression queryExpression = null; |
| if (joinFetch.hasIdentificationVariable()){ |
| String identificationVariable = queryContext.literal(joinFetch, LiteralType.IDENTIFICATION_VARIABLE); |
| queryExpression =queryContext.findQueryExpression(identificationVariable); |
| //Join expression has identification variable and was processed in the From clause |
| } |
| if (queryExpression == null){ |
| queryExpression = queryContext.buildExpression(joinFetch); |
| } |
| query.addJoinedAttribute(queryExpression); |
| } |
| } |
| } |
| } |
| |
| private static class CountFunctionVisitor extends EclipseLinkAnonymousExpressionVisitor { |
| |
| /** |
| * Determines whether the single {@link org.eclipse.persistence.jpa.jpql.parser.Expression |
| * Expression} is the <b>COUNT</b> expression. |
| */ |
| boolean hasCountFunction; |
| |
| @Override |
| public void visit(CountFunction expression) { |
| hasCountFunction = true; |
| } |
| |
| @Override |
| protected void visit(org.eclipse.persistence.jpa.jpql.parser.Expression expression) { |
| hasCountFunction = false; |
| } |
| |
| @Override |
| public void visit(SelectClause expression) { |
| expression.getSelectExpression().accept(this); |
| } |
| |
| @Override |
| public void visit(SimpleSelectClause expression) { |
| expression.getSelectExpression().accept(this); |
| } |
| } |
| |
| private class JoinVisitor extends AbstractEclipseLinkExpressionVisitor { |
| |
| /** |
| * Cache the Expression associated with the identification variable so it can be used in the |
| * visit(Join) in order to properly create the ON clause expression. |
| */ |
| private Expression baseExpression; |
| |
| private Expression addNonFetchJoinedAttribute(org.eclipse.persistence.jpa.jpql.parser.Expression expression, |
| IdentificationVariable identificationVariable) { |
| |
| String variableName = identificationVariable.getVariableName(); |
| |
| // Always add the expression, as it may not be defined elsewhere, |
| // unless it has already been defined as the builder. |
| Expression queryExpression = queryContext.getQueryExpression(variableName); |
| |
| if (queryExpression == null) { |
| queryExpression = queryContext.buildExpression(expression); |
| queryContext.addQueryExpression(variableName, queryExpression); |
| } |
| |
| ObjectLevelReadQuery query = queryContext.getDatabaseQuery(); |
| |
| if (query.getExpressionBuilder() != queryExpression) { |
| query.addNonFetchJoinedAttribute(queryExpression); |
| } |
| |
| return queryExpression; |
| } |
| |
| @Override |
| public void visit(CollectionExpression expression) { |
| expression.acceptChildren(this); |
| } |
| |
| @Override |
| public void visit(CollectionMemberDeclaration expression) { |
| addNonFetchJoinedAttribute( |
| expression, |
| (IdentificationVariable) expression.getIdentificationVariable() |
| ); |
| } |
| |
| @Override |
| public void visit(FromClause expression) { |
| expression.getDeclaration().accept(this); |
| } |
| |
| @Override |
| public void visit(IdentificationVariableDeclaration expression) { |
| |
| expression.getRangeVariableDeclaration().accept(this); |
| |
| if (expression.hasJoins()) { |
| expression.getJoins().accept(this); |
| } |
| } |
| |
| @Override |
| public void visit(Join expression) { |
| |
| if (expression.hasIdentificationVariable()) { |
| |
| IdentificationVariable identificationVariable = (IdentificationVariable) expression.getIdentificationVariable(); |
| Expression queryExpression = null; |
| |
| if (expression.hasFetch()){ |
| String variableName = identificationVariable.getVariableName(); |
| queryExpression = queryContext.getQueryExpression(variableName); |
| |
| if (queryExpression == null) { |
| queryExpression = queryContext.buildExpression(expression); |
| queryContext.addQueryExpression(variableName, queryExpression); |
| } |
| } else { |
| queryExpression = addNonFetchJoinedAttribute(expression, identificationVariable); |
| } |
| |
| // Add the ON clause to the expression |
| if (expression.hasOnClause()) { |
| Expression onClause = queryContext.buildExpression(expression.getOnClause()); |
| |
| // Create the JOIN expression using the base Expression |
| //'queryExpression' will be altered pass-by-reference |
| if (expression.isLeftJoin()) { |
| baseExpression.leftJoin(queryExpression, onClause); |
| } else { |
| baseExpression.join(queryExpression, onClause); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void visit(RangeVariableDeclaration expression) { |
| baseExpression = addNonFetchJoinedAttribute( |
| expression, |
| (IdentificationVariable) expression.getIdentificationVariable() |
| ); |
| } |
| |
| @Override |
| public void visit(SimpleFromClause expression) { |
| expression.getDeclaration().accept(this); |
| } |
| } |
| |
| private class OneToOneSelectedVisitor extends EclipseLinkAnonymousExpressionVisitor { |
| |
| /** |
| * Determines whether the visited {@link org.eclipse.persistence.jpa.jpql.parser.Expression |
| * Expression} represents a relationship. |
| */ |
| boolean oneToOneSelected; |
| |
| @Override |
| public void visit(AvgFunction expression) { |
| expression.getExpression().accept(this); |
| } |
| |
| @Override |
| public void visit(CollectionExpression expression) { |
| for (org.eclipse.persistence.jpa.jpql.parser.Expression child : expression.children()) { |
| child.accept(this); |
| if (oneToOneSelected) { |
| break; |
| } |
| } |
| } |
| |
| @Override |
| public void visit(ConstructorExpression expression) { |
| expression.getConstructorItems().accept(this); |
| } |
| |
| @Override |
| public void visit(CountFunction expression) { |
| oneToOneSelected = false; |
| } |
| |
| @Override |
| public void visit(IdentificationVariable expression) { |
| oneToOneSelected = !queryContext.isRangeIdentificationVariable(expression.getVariableName()); |
| } |
| |
| @Override |
| public void visit(KeyExpression expression) { |
| oneToOneSelected = true; |
| } |
| |
| @Override |
| public void visit(MaxFunction expression) { |
| expression.getExpression().accept(this); |
| } |
| |
| @Override |
| public void visit(MinFunction expression) { |
| expression.getExpression().accept(this); |
| } |
| |
| @Override |
| public void visit(ObjectExpression expression) { |
| expression.getExpression().accept(this); |
| } |
| |
| @Override |
| protected void visit(org.eclipse.persistence.jpa.jpql.parser.Expression expression) { |
| oneToOneSelected = true; |
| } |
| |
| @Override |
| public void visit(ResultVariable expression) { |
| expression.getSelectExpression().accept(this); |
| } |
| |
| @Override |
| public void visit(SelectClause expression) { |
| expression.getSelectExpression().accept(this); |
| } |
| |
| @Override |
| public void visit(SimpleSelectClause expression) { |
| expression.getSelectExpression().accept(this); |
| } |
| |
| @Override |
| public void visit(StateFieldPathExpression expression) { |
| DatabaseMapping mapping = queryContext.resolveMapping(expression); |
| oneToOneSelected = (mapping != null) && !mapping.isDirectToFieldMapping(); |
| } |
| |
| @Override |
| public void visit(SumFunction expression) { |
| expression.getExpression().accept(this); |
| } |
| |
| @Override |
| public void visit(ValueExpression expression) { |
| oneToOneSelected = true; |
| } |
| } |
| } |