/**
   * INTERNAL: Return the expression to join the main table of this node to any auxiliary tables.
   */
  public Expression additionalExpressionCriteria() {
    if (getDescriptor() == null) {
      return null;
    }

    Expression criteria = getDescriptor().getQueryManager().getAdditionalJoinExpression();
    if (criteria != null) {
      criteria = getBaseExpression().twist(criteria, this);
      if (shouldUseOuterJoin() && getSession().getPlatform().shouldPrintOuterJoinInWhereClause()) {
        criteria.convertToUseOuterJoin();
      }
    }
    if (getSession().getPlatform().shouldPrintOuterJoinInWhereClause()) {
      if (isUsingOuterJoinForMultitableInheritance()) {
        Expression childrenCriteria =
            getDescriptor().getInheritancePolicy().getChildrenJoinExpression();
        childrenCriteria = getBaseExpression().twist(childrenCriteria, this);
        childrenCriteria.convertToUseOuterJoin();
        if (criteria == null) {
          criteria = childrenCriteria;
        } else {
          criteria = criteria.and(childrenCriteria);
        }
      }
    }
    if ((getDescriptor() != null) && (getDescriptor().getHistoryPolicy() != null)) {
      Expression historyCriteria =
          getDescriptor().getHistoryPolicy().additionalHistoryExpression(this);
      if (criteria != null) {
        criteria = criteria.and(historyCriteria);
      } else {
        criteria = historyCriteria;
      }
    }
    return criteria;
  }
  /**
   * INTERNAL: Selection criteria is created with source foreign keys and target keys. This criteria
   * is then used to read target records from the table.
   */
  public void initializeSelectionCriteria(AbstractSession session) {
    Expression selectionCriteria = null;
    Expression expression;

    ExpressionBuilder expBuilder = new ExpressionBuilder();

    Iterator sourceKeysEnum = getSourceToTargetQueryKeyNames().keySet().iterator();

    while (sourceKeysEnum.hasNext()) {
      DatabaseField sourceKey = (DatabaseField) sourceKeysEnum.next();
      String target = (String) this.getSourceToTargetQueryKeyNames().get(sourceKey);
      expression = expBuilder.getParameter(sourceKey).equal(expBuilder.get(target));

      if (selectionCriteria == null) {
        selectionCriteria = expression;
      } else {
        selectionCriteria = expression.and(selectionCriteria);
      }
    }

    setSelectionCriteria(selectionCriteria);
  }
  /**
   * INTERNAL: For CR#2456 if this is part of an objExp.equal(objExp), do not need to add additional
   * expressions to normalizer both times, and the foreign key join replaces the equal expression.
   */
  public Expression normalize(ExpressionNormalizer normalizer, Vector foreignKeyJoinPointer) {
    if (hasBeenNormalized()) {
      return this;
    }
    super.normalize(normalizer);

    setHasBeenNormalized(true);
    if ((getMapping() != null) && getMapping().isDirectToXMLTypeMapping()) {
      normalizer.getStatement().setRequiresAliases(true);
    }

    // Check if any joins need to be added.
    if (isAttribute()) {
      return this;
    }

    // If the mapping is 'ref' or 'structure', no join needed.
    if ((getMapping() != null)
        && (getMapping().isReferenceMapping() || getMapping().isStructureMapping())) {
      normalizer.getStatement().setRequiresAliases(true);
      return this;
    }

    // Compute if a distinct is required during normalization.
    if (shouldQueryToManyRelationship()
        && (!normalizer.getStatement().isDistinctComputed())
        && (!normalizer.getStatement().isAggregateSelect())) {
      normalizer.getStatement().useDistinct();
    }

    // Turn off DISTINCT if nestedTableMapping is used (not supported by Oracle 8.1.5).
    if ((getMapping() != null) && getMapping().isNestedTableMapping()) {
      // There are two types of nested tables, one used by clients, one used by mappings, do nothing
      // in the mapping case.
      if (!shouldQueryToManyRelationship()) {
        return this;
      }
      normalizer.getStatement().dontUseDistinct();
    }

    Expression mappingExpression = mappingCriteria();
    if (mappingExpression != null) {
      mappingExpression = mappingExpression.normalize(normalizer);
    }
    if (mappingExpression != null) {
      // If the join was an outer join we must not add the join criteria to the where clause,
      // if the platform prints the join in the from clause.
      if (shouldUseOuterJoin() && (getSession().getPlatform().isInformixOuterJoin())) {
        normalizer.getStatement().getOuterJoinExpressions().addElement(this);
        normalizer.getStatement().getOuterJoinedMappingCriteria().addElement(mappingExpression);
        normalizer.addAdditionalExpression(mappingExpression.and(additionalExpressionCriteria()));
        return this;
      } else if ((shouldUseOuterJoin() || isUsingOuterJoinForMultitableInheritance())
          && (!getSession().getPlatform().shouldPrintOuterJoinInWhereClause())) {
        if (shouldUseOuterJoin()) {
          normalizer.getStatement().getOuterJoinExpressions().addElement(this);
          normalizer.getStatement().getOuterJoinedMappingCriteria().addElement(mappingExpression);
          normalizer
              .getStatement()
              .getOuterJoinedAdditionalJoinCriteria()
              .addElement(additionalExpressionCriteriaMap());
          normalizer.getStatement().getDescriptorsForMultitableInheritanceOnly().add(null);
          return this;
        } else {
          if (isUsingOuterJoinForMultitableInheritance()) {
            normalizer.getStatement().getOuterJoinExpressions().addElement(null);
            normalizer.getStatement().getOuterJoinedMappingCriteria().addElement(null);
            normalizer
                .getStatement()
                .getOuterJoinedAdditionalJoinCriteria()
                .addElement(additionalExpressionCriteriaMap());
            normalizer
                .getStatement()
                .getDescriptorsForMultitableInheritanceOnly()
                .add(getMapping().getReferenceDescriptor());
            // fall through to the main case
          }
        }
      }

      // This must be added even if outer. Actually it should be converted to use a right outer
      // join, but that gets complex
      // so we do not support this current which is a limitation in some cases.
      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(mappingExpression);
      } else {
        normalizer.addAdditionalExpression(mappingExpression.and(additionalExpressionCriteria()));
      }
    }

    // For bug 2900974 special code for DirectCollectionMappings moved to printSQL.
    return this;
  }