protected void validateJoin()

in core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java [3732:3856]


  protected void validateJoin(SqlJoin join, SqlValidatorScope scope) {
    final SqlNode left = join.getLeft();
    final SqlNode right = join.getRight();
    final boolean natural = join.isNatural();
    final JoinType joinType = join.getJoinType();
    final JoinConditionType conditionType = join.getConditionType();
    final SqlValidatorScope joinScope = getScopeOrThrow(join); // getJoinScope?
    validateFrom(left, unknownType, joinScope);
    validateFrom(right, unknownType, joinScope);

    // Validate condition.
    switch (conditionType) {
    case NONE:
      checkArgument(join.getCondition() == null);
      break;
    case ON:
      final SqlNode condition = expand(getCondition(join), joinScope);
      join.setOperand(5, condition);
      validateWhereOrOn(joinScope, condition, "ON");
      checkRollUp(null, join, condition, joinScope, "ON");
      break;
    case USING:
      @SuppressWarnings({"rawtypes", "unchecked"}) List<SqlIdentifier> list =
          (List) getCondition(join);

      // Parser ensures that using clause is not empty.
      checkArgument(!list.isEmpty(), "Empty USING clause");
      for (SqlIdentifier id : list) {
        validateCommonJoinColumn(id, left, right, scope, natural);
      }
      break;
    default:
      throw Util.unexpected(conditionType);
    }

    // Validate NATURAL.
    if (natural) {
      if (join.getCondition() != null) {
        throw newValidationError(getCondition(join),
            RESOURCE.naturalDisallowsOnOrUsing());
      }

      // Join on fields that occur on each side.
      // Check compatibility of the chosen columns.
      for (String name : deriveNaturalJoinColumnList(join)) {
        final SqlIdentifier id =
            new SqlIdentifier(name, join.isNaturalNode().getParserPosition());
        validateCommonJoinColumn(id, left, right, scope, natural);
      }
    }

    // Which join types require/allow a ON/USING condition, or allow
    // a NATURAL keyword?
    switch (joinType) {
    case LEFT_ANTI_JOIN:
    case LEFT_SEMI_JOIN:
      if (!this.config.conformance().isLiberal()) {
        throw newValidationError(join.getJoinTypeNode(),
            RESOURCE.dialectDoesNotSupportFeature(joinType.name()));
      }
      // fall through
    case INNER:
    case LEFT:
    case RIGHT:
    case FULL:
      if ((join.getCondition() == null) && !natural) {
        throw newValidationError(join, RESOURCE.joinRequiresCondition());
      }
      break;
    case COMMA:
    case CROSS:
      if (join.getCondition() != null) {
        throw newValidationError(join.getConditionTypeNode(),
            RESOURCE.crossJoinDisallowsCondition());
      }
      if (natural) {
        throw newValidationError(join.getConditionTypeNode(),
            RESOURCE.crossJoinDisallowsCondition());
      }
      break;
    case LEFT_ASOF:
    case ASOF: {
      // In addition to the standard join checks, the ASOF join requires the
      // ON conditions to be a conjunction of simple equalities from both relations.
      SqlAsofJoin asof = (SqlAsofJoin) join;
      SqlNode matchCondition = getMatchCondition(asof);
      matchCondition = expand(matchCondition, joinScope);
      join.setOperand(6, matchCondition);
      validateWhereOrOn(joinScope, matchCondition, "MATCH_CONDITION");
      SqlNode condition = join.getCondition();
      if (condition == null) {
        throw newValidationError(join, RESOURCE.joinRequiresCondition());
      }
      ConjunctionOfEqualities conj = new ConjunctionOfEqualities();
      condition.accept(conj);
      if (conj.illegal) {
        throw newValidationError(condition, RESOURCE.asofConditionMustBeComparison());
      }

      CompareFromBothSides validateCompare =
          new CompareFromBothSides(joinScope,
              catalogReader, RESOURCE.asofConditionMustBeComparison());
      condition.accept(validateCompare);

      // It also requires the MATCH condition to be a comparison.
      if (!(matchCondition instanceof SqlCall)) {
        throw newValidationError(matchCondition, RESOURCE.asofMatchMustBeComparison());
      }
      SqlCall matchCall = (SqlCall) matchCondition;
      SqlOperator operator = matchCall.getOperator();
      if (!SqlKind.ORDER_COMPARISON.contains(operator.kind)) {
        throw newValidationError(matchCondition, RESOURCE.asofMatchMustBeComparison());
      }

      // Change the exception in validateCompare when we validate the match condition
      validateCompare =
          new CompareFromBothSides(joinScope,
              catalogReader, RESOURCE.asofMatchMustBeComparison());
      matchCondition.accept(validateCompare);
      break;
    }
    default:
      throw Util.unexpected(joinType);
    }
  }