org.eclipse.persistence.internal.jpa.jpql.AbstractObjectLevelReadQueryVisitor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org.eclipse.persistence.core Show documentation
Show all versions of org.eclipse.persistence.core Show documentation
EclipseLink build based upon Git transaction ecdf3c32c4
/*
* Copyright (c) 2006, 2019 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
* SELECT 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 ReadAllQueryVisitor
.
*
* @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;
}
/**
* {@inheritDoc}
*/
@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);
}
/**
* {@inheritDoc}
*/
@Override
public void visit(CollectionExpression expression) {
expression.acceptChildren(this);
}
/**
* {@inheritDoc}
*/
@Override
public void visit(FromClause expression) {
visitAbstractFromClause(expression);
}
/**
* {@inheritDoc}
*/
@Override
public void visit(IdentificationVariable expression) {
visitIdentificationVariable(expression);
}
/**
* {@inheritDoc}
*/
@Override
public void visit(ObjectExpression expression) {
expression.getExpression().accept(this);
}
/**
* {@inheritDoc}
*/
@Override
public void visit(OrderByClause expression) {
expression.getOrderByItems().accept(this);
}
/**
* {@inheritDoc}
*/
@Override
public void visit(OrderByItem expression) {
Expression queryExpression = queryContext.buildExpression(expression);
query.addOrdering(queryExpression);
}
/**
* {@inheritDoc}
*/
@Override
public void visit(SelectClause expression) {
// Select couple flags
visitAbstractSelectClause(expression);
}
/**
* {@inheritDoc}
*/
@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);
}
}
/**
* {@inheritDoc}
*/
@Override
public void visit(SimpleFromClause expression) {
visitAbstractFromClause(expression);
}
/**
* {@inheritDoc}
*/
@Override
public void visit(SimpleSelectClause expression) {
// Select couple flags
visitAbstractSelectClause(expression);
}
/**
* {@inheritDoc}
*/
@Override
public void visit(SimpleSelectStatement expression) {
// Handle SELECT/FROM/WHERE
visitAbstractSelectStatement(expression);
}
/**
* {@inheritDoc}
*/
@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);
}
/**
* {@inheritDoc}
*/
@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 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 COUNT expression.
*/
boolean hasCountFunction;
/**
* {@inheritDoc}
*/
@Override
public void visit(CountFunction expression) {
hasCountFunction = true;
}
/**
* {@inheritDoc}
*/
@Override
protected void visit(org.eclipse.persistence.jpa.jpql.parser.Expression expression) {
hasCountFunction = false;
}
/**
* {@inheritDoc}
*/
@Override
public void visit(SelectClause expression) {
expression.getSelectExpression().accept(this);
}
/**
* {@inheritDoc}
*/
@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 = (ObjectLevelReadQuery) queryContext.getDatabaseQuery();
if (query.getExpressionBuilder() != queryExpression) {
query.addNonFetchJoinedAttribute(queryExpression);
}
return queryExpression;
}
/**
* {@inheritDoc}
*/
@Override
public void visit(CollectionExpression expression) {
expression.acceptChildren(this);
}
/**
* {@inheritDoc}
*/
@Override
public void visit(CollectionMemberDeclaration expression) {
addNonFetchJoinedAttribute(
expression,
(IdentificationVariable) expression.getIdentificationVariable()
);
}
/**
* {@inheritDoc}
*/
@Override
public void visit(FromClause expression) {
expression.getDeclaration().accept(this);
}
/**
* {@inheritDoc}
*/
@Override
public void visit(IdentificationVariableDeclaration expression) {
expression.getRangeVariableDeclaration().accept(this);
if (expression.hasJoins()) {
expression.getJoins().accept(this);
}
}
/**
* {@inheritDoc}
*/
@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);
}
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void visit(RangeVariableDeclaration expression) {
baseExpression = addNonFetchJoinedAttribute(
expression,
(IdentificationVariable) expression.getIdentificationVariable()
);
}
/**
* {@inheritDoc}
*/
@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;
/**
* {@inheritDoc}
*/
@Override
public void visit(AvgFunction expression) {
expression.getExpression().accept(this);
}
/**
* {@inheritDoc}
*/
@Override
public void visit(CollectionExpression expression) {
for (org.eclipse.persistence.jpa.jpql.parser.Expression child : expression.children()) {
child.accept(this);
if (oneToOneSelected) {
break;
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void visit(ConstructorExpression expression) {
expression.getConstructorItems().accept(this);
}
/**
* {@inheritDoc}
*/
@Override
public void visit(CountFunction expression) {
oneToOneSelected = false;
}
/**
* {@inheritDoc}
*/
@Override
public void visit(IdentificationVariable expression) {
oneToOneSelected = !queryContext.isRangeIdentificationVariable(expression.getVariableName());
}
/**
* {@inheritDoc}
*/
@Override
public void visit(KeyExpression expression) {
oneToOneSelected = true;
}
/**
* {@inheritDoc}
*/
@Override
public void visit(MaxFunction expression) {
expression.getExpression().accept(this);
}
/**
* {@inheritDoc}
*/
@Override
public void visit(MinFunction expression) {
expression.getExpression().accept(this);
}
/**
* {@inheritDoc}
*/
@Override
public void visit(ObjectExpression expression) {
expression.getExpression().accept(this);
}
/**
* {@inheritDoc}
*/
@Override
protected void visit(org.eclipse.persistence.jpa.jpql.parser.Expression expression) {
oneToOneSelected = true;
}
/**
* {@inheritDoc}
*/
@Override
public void visit(ResultVariable expression) {
expression.getSelectExpression().accept(this);
}
/**
* {@inheritDoc}
*/
@Override
public void visit(SelectClause expression) {
expression.getSelectExpression().accept(this);
}
/**
* {@inheritDoc}
*/
@Override
public void visit(SimpleSelectClause expression) {
expression.getSelectExpression().accept(this);
}
/**
* {@inheritDoc}
*/
@Override
public void visit(StateFieldPathExpression expression) {
DatabaseMapping mapping = queryContext.resolveMapping(expression);
oneToOneSelected = (mapping != null) && !mapping.isDirectToFieldMapping();
}
/**
* {@inheritDoc}
*/
@Override
public void visit(SumFunction expression) {
expression.getExpression().accept(this);
}
/**
* {@inheritDoc}
*/
@Override
public void visit(ValueExpression expression) {
oneToOneSelected = true;
}
}
}