blob: ab08f2d1c9cd171333c40d00e9e44151be1ea7ba [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.calcite.sql;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.util.SqlString;
import org.apache.calcite.util.ImmutableNullableList;
import org.apache.calcite.util.Util;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.List;
import java.util.function.UnaryOperator;
import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Objects.requireNonNull;
/**
* Parse tree node representing a {@code JOIN} clause.
*/
public class SqlJoin extends SqlCall {
static final SqlJoinOperator COMMA_OPERATOR =
new SqlJoinOperator("COMMA-JOIN", 16);
public static final SqlJoinOperator OPERATOR =
new SqlJoinOperator("JOIN", 18);
SqlNode left;
/**
* Operand says whether this is a natural join. Must be constant TRUE or
* FALSE.
*/
SqlLiteral natural;
/**
* Value must be a {@link SqlLiteral}, one of the integer codes for
* {@link JoinType}.
*/
SqlLiteral joinType;
SqlNode right;
/**
* Value must be a {@link SqlLiteral}, one of the integer codes for
* {@link JoinConditionType}.
*/
SqlLiteral conditionType;
@Nullable SqlNode condition;
//~ Constructors -----------------------------------------------------------
public SqlJoin(SqlParserPos pos, SqlNode left, SqlLiteral natural,
SqlLiteral joinType, SqlNode right, SqlLiteral conditionType,
@Nullable SqlNode condition) {
super(pos);
this.left = left;
this.natural = requireNonNull(natural, "natural");
this.joinType = requireNonNull(joinType, "joinType");
this.right = right;
this.conditionType = requireNonNull(conditionType, "conditionType");
this.condition = condition;
checkArgument(natural.getTypeName() == SqlTypeName.BOOLEAN);
conditionType.getValueAs(JoinConditionType.class);
joinType.getValueAs(JoinType.class);
}
//~ Methods ----------------------------------------------------------------
@Override public SqlOperator getOperator() {
//noinspection SwitchStatementWithTooFewBranches
switch (getJoinType()) {
case COMMA:
return COMMA_OPERATOR;
default:
return OPERATOR;
}
}
@Override public SqlKind getKind() {
return SqlKind.JOIN;
}
@SuppressWarnings("nullness")
@Override public List<SqlNode> getOperandList() {
return ImmutableNullableList.of(left, natural, joinType, right,
conditionType, condition);
}
@SuppressWarnings("assignment.type.incompatible")
@Override public void setOperand(int i, @Nullable SqlNode operand) {
switch (i) {
case 0:
left = operand;
break;
case 1:
natural = (SqlLiteral) operand;
break;
case 2:
joinType = (SqlLiteral) operand;
break;
case 3:
right = operand;
break;
case 4:
conditionType = (SqlLiteral) operand;
break;
case 5:
condition = operand;
break;
default:
throw new AssertionError(i);
}
}
public final @Nullable SqlNode getCondition() {
return condition;
}
/** Returns a {@link JoinConditionType}, never null. */
public final JoinConditionType getConditionType() {
return conditionType.getValueAs(JoinConditionType.class);
}
public SqlLiteral getConditionTypeNode() {
return conditionType;
}
/** Returns a {@link JoinType}, never null. */
public final JoinType getJoinType() {
return joinType.getValueAs(JoinType.class);
}
public SqlLiteral getJoinTypeNode() {
return joinType;
}
public final SqlNode getLeft() {
return left;
}
public void setLeft(SqlNode left) {
this.left = left;
}
public final boolean isNatural() {
return natural.booleanValue();
}
public final SqlLiteral isNaturalNode() {
return natural;
}
public final SqlNode getRight() {
return right;
}
public void setRight(SqlNode right) {
this.right = right;
}
/** Describes the syntax of the SQL {@code JOIN} operator.
*
* <p>A variant describes the comma operator, which has lower precedence.
*/
public static class SqlJoinOperator extends SqlOperator {
private static final SqlWriter.FrameType FRAME_TYPE =
SqlWriter.FrameTypeEnum.create("USING");
//~ Constructors -----------------------------------------------------------
private SqlJoinOperator(String name, int prec) {
super(name, SqlKind.JOIN, prec, true, null, null, null);
}
//~ Methods ----------------------------------------------------------------
@Override public SqlSyntax getSyntax() {
return SqlSyntax.SPECIAL;
}
@SuppressWarnings("argument.type.incompatible")
@Override public SqlCall createCall(
@Nullable SqlLiteral functionQualifier,
SqlParserPos pos,
@Nullable SqlNode... operands) {
assert functionQualifier == null;
return new SqlJoin(pos, operands[0], (SqlLiteral) operands[1],
(SqlLiteral) operands[2], operands[3], (SqlLiteral) operands[4],
operands[5]);
}
@Override public void unparse(
SqlWriter writer,
SqlCall call,
int leftPrec,
int rightPrec) {
final SqlJoin join = (SqlJoin) call;
join.left.unparse(
writer,
leftPrec,
getLeftPrec());
switch (join.getJoinType()) {
case COMMA:
writer.sep(",", true);
break;
case CROSS:
writer.sep(join.isNatural() ? "NATURAL CROSS JOIN" : "CROSS JOIN");
break;
case FULL:
writer.sep(join.isNatural() ? "NATURAL FULL JOIN" : "FULL JOIN");
break;
case INNER:
writer.sep(join.isNatural() ? "NATURAL INNER JOIN" : "INNER JOIN");
break;
case LEFT:
writer.sep(join.isNatural() ? "NATURAL LEFT JOIN" : "LEFT JOIN");
break;
case LEFT_SEMI_JOIN:
writer.sep(join.isNatural() ? "NATURAL LEFT SEMI JOIN"
: "LEFT SEMI JOIN");
break;
case LEFT_ANTI_JOIN:
writer.sep(join.isNatural() ? "NATURAL LEFT ANTI JOIN"
: "LEFT ANTI JOIN");
break;
case RIGHT:
writer.sep(join.isNatural() ? "NATURAL RIGHT JOIN" : "RIGHT JOIN");
break;
default:
throw Util.unexpected(join.getJoinType());
}
join.right.unparse(writer, getRightPrec(), rightPrec);
SqlNode joinCondition = join.condition;
if (joinCondition != null) {
switch (join.getConditionType()) {
case USING:
// No need for an extra pair of parens -- the condition is a
// list. The result is something like "USING (deptno, gender)".
writer.keyword("USING");
assert joinCondition instanceof SqlNodeList
: "joinCondition should be SqlNodeList, got " + joinCondition;
final SqlWriter.Frame frame =
writer.startList(FRAME_TYPE, "(", ")");
joinCondition.unparse(writer, 0, 0);
writer.endList(frame);
break;
case ON:
writer.keyword("ON");
joinCondition.unparse(writer, leftPrec, rightPrec);
break;
default:
throw Util.unexpected(join.getConditionType());
}
}
}
}
@Override public SqlString toSqlString(UnaryOperator<SqlWriterConfig> transform) {
SqlNode selectWrapper =
new SqlSelect(SqlParserPos.ZERO, SqlNodeList.EMPTY,
SqlNodeList.SINGLETON_STAR, this, null, null, null,
SqlNodeList.EMPTY, null, null, null, null, SqlNodeList.EMPTY);
return selectWrapper.toSqlString(transform);
}
}