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