blob: 7e525179d762a7bfc5623c52b320f503e8dbd2ea [file] [log] [blame]
/*
* Copyright (c) 2016, 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:
// 11/06/2013-2.5.1 Chris Delahunt
// - 374771 : TREAT support
package org.eclipse.persistence.internal.expressions;
import java.io.BufferedWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.InheritancePolicy;
import org.eclipse.persistence.exceptions.QueryException;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.DatabaseTable;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.queries.DatabaseQuery;
/**
* @author cdelahun
*
*/
public class TreatAsExpression extends QueryKeyExpression {
protected ObjectExpression typeExpressionBase;
protected Expression typeExpression;
protected Boolean isDowncast;//we only need a type expression if this is a downcast.
@Override
public Expression convertToUseOuterJoin() {
typeExpressionBase.convertToUseOuterJoin();
return this;
}
@Override
public String descriptionOfNodeType() {
return "Treat";
}
@Override
public boolean equals(Object object) {
if (!super.equals(object)) {
return false;
}
TreatAsExpression expression = (TreatAsExpression) object;
return getCastClass().equals(expression.getCastClass());
}
@Override
public Vector getFields() {
return typeExpressionBase.getFields();
}
@Override
public Object getFieldValue(Object objectValue, AbstractSession session) {
return typeExpressionBase.getFieldValue(objectValue, session);
}
/**
* This owns (can access) the child's extra tables as well as its parent's tables
* so we should pull these from super (which gets them from the current descriptor)
*/
@Override
public List<DatabaseTable> getOwnedTables() {
return super.getOwnedTables();
}
@Override
public Expression getAlias(Expression subSelect) {
return typeExpressionBase.getAlias(subSelect);
}
/**
* Calculate the relation table for based on the various QueryKeyExpression
* usages (join query keys, custom defined query keys, or query keys for
* mappings).
* Does not apply to Treat
*
* Called from {@link SQLSelectStatement#appendFromClauseForOuterJoin}.
*
* @return DatabaseTable
*/
@Override
public DatabaseTable getRelationTable() {
return null;
}
@Override
public TableAliasLookup getTableAliases() {
return typeExpressionBase.getTableAliases();
}
@Override
public boolean hasAsOfClause() {
return typeExpressionBase.hasAsOfClause();
}
/**
* INTERNAL:
*/
public boolean isDowncast() {
if (this.isDowncast == null) {
this.getDescriptor();//initializes isDowncast
}
return this.isDowncast;
}
/**
* INTERNAL:
*/
@Override
public boolean isTreatExpression() {
return true;
}
@Override
public void printSQL(ExpressionSQLPrinter printer) {
typeExpressionBase.printSQL(printer);
}
@Override
public boolean selectIfOrderedBy() {
return typeExpressionBase.selectIfOrderedBy();
}
@Override
public Expression twistedForBaseAndContext(Expression newBase,
Expression context, Expression oldBase) {
if (oldBase == null || this.typeExpressionBase == oldBase) {
Expression twistedBase = this.typeExpressionBase.twistedForBaseAndContext(newBase, context, oldBase);
TreatAsExpression result = (TreatAsExpression)twistedBase.treat(this.castClass);
if (shouldUseOuterJoin) {
result.doUseOuterJoin();
}
if (shouldQueryToManyRelationship) {
result.doQueryToManyRelationship();
}
return result;
}
return this;
}
@Override
public void validateNode() {
typeExpressionBase.validateNode();
//getDescriptor currently checks if the descriptor can be found for the castclass.
//We may want to check that this is a downcast in future
getDescriptor();
}
@Override
public Object valueFromObject(Object object, AbstractSession session,
AbstractRecord translationRow, int valueHolderPolicy,
boolean isObjectUnregistered) {
return typeExpressionBase.valueFromObject(object, session,
translationRow, valueHolderPolicy, isObjectUnregistered);
}
@Override
public Object valueFromObject(Object object, AbstractSession session,
AbstractRecord translationRow, int valueHolderPolicy) {
return typeExpressionBase.valueFromObject(object, session,
translationRow, valueHolderPolicy);
}
@Override
public void writeDescriptionOn(BufferedWriter writer) throws IOException {
if (castClass != null){
writer.write(" AS "+ castClass.getName());
}
}
@Override
public void writeFields(ExpressionSQLPrinter printer, List<DatabaseField> newFields, SQLSelectStatement statement) {
typeExpressionBase.writeFields(printer, newFields, statement);
}
@Override
public void writeSubexpressionsTo(BufferedWriter writer, int indent)
throws IOException {
if (this.typeExpressionBase != null) {
this.typeExpressionBase.toString(writer, indent);
} else {
super.writeSubexpressionsTo(writer, indent);
}
}
@Override
public ClassDescriptor getLeafDescriptor(DatabaseQuery query,
ClassDescriptor rootDescriptor, AbstractSession session) {
return session.getDescriptor(castClass);
}
@Override
public DatabaseMapping getLeafMapping(DatabaseQuery query,
ClassDescriptor rootDescriptor, AbstractSession session) {
return typeExpressionBase
.getLeafMapping(query, rootDescriptor, session);
}
@Override
public DatabaseTable aliasForTable(DatabaseTable table) {
return typeExpressionBase.aliasForTable(table);
}
/**
* INTERNAL:
* This expression is built on a different base than the one we want. Rebuild it and
* return the root of the new tree
*/
@Override
public Expression rebuildOn(Expression newBase) {
Expression newLocalBase = this.typeExpressionBase.rebuildOn(newBase);
return newLocalBase.treat(castClass);
}
@Override
public ClassDescriptor getDescriptor() {
if (isAttribute()) {
//TODO: add support for treat on attributes
throw QueryException.couldNotFindCastDescriptor(castClass, getBaseExpression());
}
if (descriptor == null) {
ClassDescriptor rootDescriptor = typeExpressionBase.getDescriptor();
descriptor = convertToCastDescriptor(rootDescriptor, getSession());
}
return descriptor;
}
/**
* INTERNAL -
* Return the descriptor which contains this query key, look in the inheritance hierarchy
* of rootDescriptor for the descriptor. Does not set the descriptor, only returns it.
*/
@Override
public ClassDescriptor convertToCastDescriptor(ClassDescriptor rootDescriptor, AbstractSession session) {
isDowncast = Boolean.FALSE;
if (castClass == null || rootDescriptor == null || rootDescriptor.getJavaClass() == castClass) {
return rootDescriptor;
}
ClassDescriptor castDescriptor = session.getClassDescriptor(castClass);
if (castDescriptor == null){
throw QueryException.couldNotFindCastDescriptor(castClass, getBaseExpression());
}
if (!castDescriptor.hasInheritance()){
throw QueryException.castMustUseInheritance(getBaseExpression());
}
ClassDescriptor parentDescriptor = castDescriptor.getInheritancePolicy().getParentDescriptor();
while (parentDescriptor != null){
if (parentDescriptor == rootDescriptor){
isDowncast = Boolean.TRUE;
return castDescriptor;
}
parentDescriptor = parentDescriptor.getInheritancePolicy().getParentDescriptor();
}
//is there value casting an emp to person in a query?
ClassDescriptor childDescriptor = rootDescriptor;
while (childDescriptor != null){
if (childDescriptor == castDescriptor){
return rootDescriptor;
}
childDescriptor = childDescriptor.getInheritancePolicy().getParentDescriptor();
}
throw QueryException.couldNotFindCastDescriptor(castClass, getBaseExpression());
}
public Expression getTypeClause() {
if (typeExpression == null) {
if (getDescriptor() !=null && isDowncast()) {
InheritancePolicy ip = this.getDescriptor().getInheritancePolicy();
if (ip.isChildDescriptor()) {//or use the isDowncast flag. Don't need to do anything if its not a downcast
//equivalent to typeExpressionBase.type().in(this.getDescriptor().getInheritancePolicy().getChildClasses())
typeExpression = ip.getWithAllSubclassesExpression();
if (typeExpression == null ) {
typeExpression = typeExpressionBase.type().equal(this.getDescriptor().getJavaClass());
} else {
typeExpression = this.typeExpressionBase.twist(typeExpression, typeExpressionBase);
}
}
} else {
typeExpression = this.getBuilder();//equivalent to an empty expression.
}
}
return typeExpression;
}
@Override
protected void postCopyIn(Map alreadyDone) {
super.postCopyIn(alreadyDone);
this.typeExpressionBase = (ObjectExpression)typeExpressionBase.copiedVersionFrom(alreadyDone);
}
public TreatAsExpression(Class castClass, ObjectExpression baseExpression) {
super();
this.name = "Treat as "+castClass;
this.typeExpressionBase = baseExpression;
if (baseExpression.isExpressionBuilder()){
this.baseExpression = baseExpression;
shouldQueryToManyRelationship = false;
hasQueryKey = false;
hasMapping = false;
} else {
this.baseExpression = baseExpression.getBaseExpression();
}
shouldUseOuterJoin = true;//this uses outerjoins to the cast class' tables by default.
this.castClass = castClass;
}
/**
* INTERNAL:
* Print java for project class generation
*/
@Override
public void printJava(ExpressionJavaPrinter printer) {
this.typeExpressionBase.printJava(printer);
if (castClass != null){
printer.printString(".treat(" + castClass.getName() + ".class)");
}
}
/**
* INTERNAL:
* Alias a particular table within this node
*/
@Override
protected void assignAlias(DatabaseTable alias, DatabaseTable table) {
if (tableAliases == null) {
if (this.typeExpressionBase!=null) {
if (this.typeExpressionBase.getTableAliases()==null) {
typeExpressionBase.setTableAliases(new TableAliasLookup());
}
tableAliases = typeExpressionBase.getTableAliases();
} else {
tableAliases = new TableAliasLookup();
}
}
tableAliases.put(alias, table);
}
/**
* INTERNAL:
* Assign aliases to any tables which I own. Start with t(initialValue),
* and return the new value of the counter , i.e. if initialValue is one
* and I have tables ADDRESS and EMPLOYEE I will assign them t1 and t2 respectively, and return 3.
*/
@Override
public int assignTableAliasesStartingAt(int initialValue) {
//This assumes that the typeExpressionBase will alias its own tables, so we only need to handle
//the extra's caused by this treat expression.
if (hasBeenAliased()) {
return initialValue;
}
int counter = initialValue;
if (this.typeExpressionBase != null) {
counter = this.typeExpressionBase.assignTableAliasesStartingAt(counter);
}
//Only difference between this and ObjectExpression's assignTableAliasesStartingAt is this uses getOwnedSubTables
// instead of getOwnedTables which returns everything.
List<DatabaseTable> ownedTables = getOwnedSubTables();
if (ownedTables != null) {
for (DatabaseTable table : ownedTables) {
assignAlias("t" + counter, table);
counter++;
}
}
this.hasBeenAliased = true;
return counter;
}
/**
* INTERNAL:
* much like getOwnedTables(), this gets the tables represented from the descriptor. Difference is this only returns local tables
* for the child casted descriptor, and excludes tables owned by the parent descriptor
*/
public List<DatabaseTable> getOwnedSubTables() {
ClassDescriptor parentDescriptor = this.typeExpressionBase.getDescriptor();
Vector<DatabaseTable> childTables = new Vector(2);
if (parentDescriptor.hasInheritance() && parentDescriptor.getInheritancePolicy().hasMultipleTableChild() ) {
List<DatabaseTable> parentTables = typeExpressionBase.getOwnedTables();
//All tables for this child, including parent tables
Vector<DatabaseTable> tables = getDescriptor().getTables();
for (DatabaseTable table : tables) {
if (!parentTables.contains(table)) {
childTables.add(table);
}
}
}
return childTables;
}
@Override
public List<DatabaseTable> getAdditionalTables() {
//called from ObjectExpression's getOwnedTables but not relevant to treat.
return null;
}
/*
* INTERNAL:
* If this query key represents a foreign reference answer the
* base expression -> foreign reference join criteria.
* This shouldn't be used on Treat
*/
@Override
public Expression mappingCriteria(Expression base) {
if (typeExpressionBase.isQueryKeyExpression()) {
return ((QueryKeyExpression)typeExpressionBase).mappingCriteria(base);
}
return null;
}
/**
* INTERNAL:
* Used in case outer joins should be printed in FROM clause.
* Each of the additional tables mapped to expressions that joins it.
*/
public Map additionalTreatExpressionCriteriaMap() {
if (getDescriptor() == null) {
return null;
}
int tableSize = 0;
HashMap tablesJoinExpressions = new HashMap();
ClassDescriptor parentDescriptor = this.typeExpressionBase.getDescriptor();
//outerjoin our parent->child tables
if (parentDescriptor.hasInheritance() &&
parentDescriptor.getInheritancePolicy().hasMultipleTableChild() ) {
Vector<DatabaseTable> tables = getDescriptor().getTables();//All this child's tables
tableSize = tables.size();
//look up the joins from the parent descriptor to our tables.
for (int i=0; i < tableSize; i++) {
DatabaseTable table = tables.elementAt(i);
Expression joinExpression = parentDescriptor.getInheritancePolicy().getChildrenTablesJoinExpressions().get(table);
//Some of our tables might be the in our parent as well, so ignore the lack of a joinExpression
if (joinExpression != null) {
joinExpression = this.baseExpression.twist(joinExpression, this);
tablesJoinExpressions.put(table, joinExpression);
}
}
}
if (isUsingOuterJoinForMultitableInheritance()) {
List<DatabaseTable> childrenTables = getDescriptor().getInheritancePolicy().getChildrenTables();
tableSize = childrenTables.size();
for (int i=0; i < tableSize; i++) {
DatabaseTable table = childrenTables.get(i);
Expression joinExpression = getDescriptor().getInheritancePolicy().getChildrenTablesJoinExpressions().get(table);
joinExpression = this.baseExpression.twist(joinExpression, this);
tablesJoinExpressions.put(table, joinExpression);
}
}
return tablesJoinExpressions;
}
/**
* INTERNAL:
* this returns a single expression to represent the join from the main table to all child descriptor tables
* Only if outer joins should be printed in the where clause
* @return Expression
*/
public Expression getTreatCriteria() {
if (getDescriptor() == null) {
return null;
}
//need to build this using just the multiple tables on this descriptor not included in the parent's join expression
Expression criteria = null;
if(getSession().getPlatform().shouldPrintOuterJoinInWhereClause()) {
Vector<DatabaseTable> tables = getDescriptor().getTables();//This child's tables
ClassDescriptor parentDescriptor = this.typeExpressionBase.getDescriptor();
int tablesSize = tables.size();
if (parentDescriptor.hasInheritance() &&
parentDescriptor.getInheritancePolicy().hasMultipleTableChild() ) {
//look up the joins from the parent descriptor to our tables.
for (int i=0; i < tablesSize; i++) {
DatabaseTable table = tables.elementAt(i);
Expression joinExpression = parentDescriptor.getInheritancePolicy().getChildrenTablesJoinExpressions().get(table);
//Some of our tables might be the in our parent as well, so ignore the lack of a joinExpression
if (joinExpression != null) {
joinExpression = this.baseExpression.twist(joinExpression, this);
if (shouldUseOuterJoin()) {
joinExpression = joinExpression.convertToUseOuterJoin();
}
criteria = joinExpression.and(criteria);
}
}
}
}
return criteria;
}
/**
* INTERNAL:
* Return the expression to join the main table of this node to any auxiliary tables.
*/
public Expression additionalTreatExpressionCriteria() {
if (getDescriptor() == null) {
return null;
}
//need to build this using just the multiple tables on this descriptor not included in the parent's join expression
Expression criteria = null;
if(getSession().getPlatform().shouldPrintOuterJoinInWhereClause()) {
if(isUsingOuterJoinForMultitableInheritance()) {
criteria = getDescriptor().getInheritancePolicy().getChildrenJoinExpression();
criteria = this.baseExpression.twist(criteria, this);
criteria.convertToUseOuterJoin();
}
}
return criteria;
}
@Override
public DatabaseTable getSourceTable() {
//not used currently, but should return the baseExpressionType table if used in the future
return null;
}
@Override
public DatabaseTable getReferenceTable() {
//not used currently, but should return the treat subclass first table if used in the future
return null;
}
@Override
public Expression normalize(ExpressionNormalizer normalizer, Expression base, List<Expression> foreignKeyJoinPointer) {
//need to determine what type this is, as it may need to change the expression its based off slightly
if (this.hasBeenNormalized) {
return this;
}
this.hasBeenNormalized = true;
Expression typeExpression = getTypeClause();
typeExpression.normalize(normalizer);
if (this.baseExpression != null) {//should never be null
// First normalize the base.
setBaseExpression(this.baseExpression.normalize(normalizer));
if (getAsOfClause() == null) {
asOf(this.baseExpression.getAsOfClause());
}
}
//This class has no validation but we should still make the method call for consistency
//bug # 2956674
//validation is moved into normalize to ensure that expressions are valid before we attempt to work with them
validateNode();
//the following is based on QueryKey.normalize
SQLSelectStatement statement = normalizer.getStatement();
//no longer directly normalize the typeExpressionBase, or find a way to use it
this.typeExpressionBase = (ObjectExpression)this.typeExpressionBase.normalize(normalizer);
// Normalize the ON clause if present. Need to use rebuild, not twist as parameters are real parameters.
if (this.onClause != null) {//not sure this is needed/valid
this.onClause = this.onClause.normalize(normalizer);
}
ClassDescriptor parentDescriptor = this.typeExpressionBase.getDescriptor();
boolean isSTI = getOwnedSubTables().isEmpty();
//only really valid if it has inheritance, but better this code than skipping it into the joins
if (isSTI) {
if (foreignKeyJoinPointer != null) {
// If this expression is right side of an objExp.equal(objExp), one
// need not add additionalExpressionCriteria twice.
// Also the join will replace the original objExp.equal(objExp).
// For CR#2456.
foreignKeyJoinPointer.add(typeExpression.and(this.onClause));
} else {
//this just and's in the entire expression to the normalizer's expression.
//Need to use this for TYPE and none-outerjoin components
normalizer.addAdditionalLocalExpression(typeExpression.and(this.onClause));
}
return this;
}
//if shouldPrintOuterJoinInWhereClause is true, this is this child's tables joined together in one expression
Expression treatJoinTableExpressions = getTreatCriteria();
boolean parentUsingOuterJoinForMultitableInheritance = typeExpressionBase.isUsingOuterJoinForMultitableInheritance();
if (treatJoinTableExpressions != null) {
treatJoinTableExpressions = treatJoinTableExpressions.normalize(normalizer);
}
Integer postition = typeExpressionBase.getOuterJoinExpIndex();
if (postition!=null ) {
if (parentUsingOuterJoinForMultitableInheritance) {
//outer join was done, so our class' tables would have been included
return this;
}
if (getSession().getPlatform().isInformixOuterJoin()) {
normalizer.addAdditionalLocalExpression(typeExpression.and(additionalTreatExpressionCriteria()).and(this.onClause));
return this;
} else if (((!getSession().getPlatform().shouldPrintOuterJoinInWhereClause()))
|| (!getSession().getPlatform().shouldPrintInnerJoinInWhereClause())) {
//Adds the left joins from treat to the base QKE joins.
Map<DatabaseTable, Expression> map = statement.getOuterJoinExpressionsHolders().get(postition).outerJoinedAdditionalJoinCriteria;
if (map !=null) {
map.putAll(additionalTreatExpressionCriteriaMap());
} else {
statement.getOuterJoinExpressionsHolders().get(postition).outerJoinedAdditionalJoinCriteria = additionalTreatExpressionCriteriaMap();
}
return this;
}
} else if (!getSession().getPlatform().shouldPrintOuterJoinInWhereClause()
|| (!getSession().getPlatform().shouldPrintInnerJoinInWhereClause())) {
//the base is not using an outer join, so we add a new one for this class' tables.
Map additionalExpMap = additionalTreatExpressionCriteriaMap();
if (additionalExpMap!=null && !additionalExpMap.isEmpty()) {
statement.addOuterJoinExpressionsHolders(additionalExpMap, parentDescriptor);
}
}
typeExpression = typeExpression.normalize(normalizer);
if (foreignKeyJoinPointer != null) {
// If this expression is right side of an objExp.equal(objExp), one
// need not add additionalExpressionCriteria twice.
// Also the join will replace the original objExp.equal(objExp).
// For CR#2456.
foreignKeyJoinPointer.add(typeExpression.and(this.onClause));
} else {
//this just and's in the entire expression to the normalizer's expression. Need to use this for TYPE and non-outerjoin components
normalizer.addAdditionalLocalExpression(typeExpression.and(additionalTreatExpressionCriteria()).and(this.onClause));
}
return this;
}
}