blob: 88afcebccbd663e5f18d8597196a8f78aed8f9f8 [file] [log] [blame]
/*
* 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;
}
}
}