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);
}
}