content: /*
   * 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.rex;
  
  import org.apache.calcite.plan.RelOptUtil;
  import org.apache.calcite.rel.RelNode;
  import org.apache.calcite.rel.type.RelDataType;
  import org.apache.calcite.rel.type.RelDataTypeFactory;
  import org.apache.calcite.rel.type.RelDataTypeField;
  import org.apache.calcite.sql.SqlKind;
  import org.apache.calcite.sql.SqlOperator;
  import org.apache.calcite.sql.fun.SqlQuantifyOperator;
  import org.apache.calcite.sql.fun.SqlStdOperatorTable;
  import org.apache.calcite.sql.type.SqlTypeName;
  import org.apache.calcite.sql.type.SqlTypeUtil;
  
  import com.google.common.collect.ImmutableList;
  
  import org.checkerframework.checker.nullness.qual.Nullable;
  
  import java.util.List;
  import java.util.Objects;
  
  import static com.google.common.base.Preconditions.checkArgument;
  
  /**
   * Scalar expression that represents an IN, EXISTS or scalar sub-query.
   */
  public class RexSubQuery extends RexCall {
    public final RelNode rel;
  
    private RexSubQuery(RelDataType type, SqlOperator op,
        ImmutableList<RexNode> operands, RelNode rel) {
      super(type, op, operands);
      this.rel = rel;
    }
  
    /** Creates an IN sub-query. */
    public static RexSubQuery in(RelNode rel, ImmutableList<RexNode> nodes) {
      final RelDataType type = type(rel, nodes);
      return new RexSubQuery(type, SqlStdOperatorTable.IN, nodes, rel);
    }
  
    /** Creates a SOME sub-query.
     *
     * <p>There is no ALL. For {@code x comparison ALL (sub-query)} use instead
     * {@code NOT (x inverse-comparison SOME (sub-query))}.
     * If {@code comparison} is {@code >}
     * then {@code negated-comparison} is {@code <=}, and so forth.
     *
     * <p>Also =SOME is rewritten into IN */
    public static RexSubQuery some(RelNode rel, ImmutableList<RexNode> nodes,
        SqlQuantifyOperator op) {
      assert op.kind == SqlKind.SOME;
  
      if (op == SqlStdOperatorTable.SOME_EQ) {
        return RexSubQuery.in(rel, nodes);
      }
      final RelDataType type = type(rel, nodes);
      return new RexSubQuery(type, op, nodes, rel);
    }
  
    static RelDataType type(RelNode rel, ImmutableList<RexNode> nodes) {
      assert rel.getRowType().getFieldCount() == nodes.size();
      final RelDataTypeFactory typeFactory = rel.getCluster().getTypeFactory();
      boolean nullable = false;
      for (RexNode node : nodes) {
        if (node.getType().isNullable()) {
          nullable = true;
        }
      }
      for (RelDataTypeField field : rel.getRowType().getFieldList()) {
        if (field.getType().isNullable()) {
          nullable = true;
        }
      }
      return typeFactory.createTypeWithNullability(
          typeFactory.createSqlType(SqlTypeName.BOOLEAN), nullable);
    }
  
    /** Creates an EXISTS sub-query. */
    public static RexSubQuery exists(RelNode rel) {
      final RelDataTypeFactory typeFactory = rel.getCluster().getTypeFactory();
      final RelDataType type = typeFactory.createSqlType(SqlTypeName.BOOLEAN);
      return new RexSubQuery(type, SqlStdOperatorTable.EXISTS,
          ImmutableList.of(), rel);
    }
  
    /** Creates an UNIQUE sub-query. */
    public static RexSubQuery unique(RelNode rel) {
      final RelDataTypeFactory typeFactory = rel.getCluster().getTypeFactory();
      final RelDataType type = typeFactory.createSqlType(SqlTypeName.BOOLEAN);
      return new RexSubQuery(type, SqlStdOperatorTable.UNIQUE,
          ImmutableList.of(), rel);
    }
  
    /** Creates a scalar sub-query. */
    public static RexSubQuery scalar(RelNode rel) {
      final List<RelDataTypeField> fieldList = rel.getRowType().getFieldList();
      if (fieldList.size() != 1) {
        throw new IllegalArgumentException();
      }
      final RelDataTypeFactory typeFactory = rel.getCluster().getTypeFactory();
      final RelDataType type =
          typeFactory.createTypeWithNullability(fieldList.get(0).getType(), true);
      return new RexSubQuery(type, SqlStdOperatorTable.SCALAR_QUERY,
          ImmutableList.of(), rel);
    }
  
    /** Creates an ARRAY sub-query. */
    public static RexSubQuery array(RelNode rel) {
      final RelDataTypeFactory typeFactory = rel.getCluster().getTypeFactory();
      final RelDataType type =
          typeFactory.createArrayType(
              SqlTypeUtil.deriveCollectionQueryComponentType(SqlTypeName.ARRAY, rel.getRowType()),
              -1L);
      return new RexSubQuery(type, SqlStdOperatorTable.ARRAY_QUERY,
          ImmutableList.of(), rel);
    }
  
    /** Creates a MULTISET sub-query. */
    public static RexSubQuery multiset(RelNode rel) {
      final RelDataTypeFactory typeFactory = rel.getCluster().getTypeFactory();
      final RelDataType type =
          typeFactory.createMultisetType(
              SqlTypeUtil.deriveCollectionQueryComponentType(SqlTypeName.MULTISET, rel.getRowType()),
              -1L);
      return new RexSubQuery(type, SqlStdOperatorTable.MULTISET_QUERY,
          ImmutableList.of(), rel);
    }
  
    /** Creates a MAP sub-query. */
    public static RexSubQuery map(RelNode rel) {
      final RelDataTypeFactory typeFactory = rel.getCluster().getTypeFactory();
      final RelDataType rowType = rel.getRowType();
      checkArgument(rowType.getFieldCount() == 2,
          "MAP requires exactly two fields, got %s; row type %s",
          rowType.getFieldCount(), rowType);
      final List<RelDataTypeField> fieldList = rowType.getFieldList();
      final RelDataType type =
          typeFactory.createMapType(fieldList.get(0).getType(),
              fieldList.get(1).getType());
      return new RexSubQuery(type, SqlStdOperatorTable.MAP_QUERY,
          ImmutableList.of(), rel);
    }
  
    @Override public <R> R accept(RexVisitor<R> visitor) {
      return visitor.visitSubQuery(this);
    }
  
    @Override public <R, P> R accept(RexBiVisitor<R, P> visitor, P arg) {
      return visitor.visitSubQuery(this, arg);
    }
  
    @Override protected String computeDigest(boolean withType) {
      final StringBuilder sb = new StringBuilder(op.getName());
      sb.append("(");
      for (RexNode operand : operands) {
        sb.append(operand);
        sb.append(", ");
      }
      sb.append("{\n");
      sb.append(RelOptUtil.toString(rel));
      sb.append("})");
      return sb.toString();
    }
  
    @Override public RexSubQuery clone(RelDataType type, List<RexNode> operands) {
      return new RexSubQuery(type, getOperator(),
          ImmutableList.copyOf(operands), rel);
    }
  
    public RexSubQuery clone(RelNode rel) {
      return new RexSubQuery(type, getOperator(), operands, rel);
    }
  
    @Override public boolean equals(@Nullable Object obj) {
      if (this == obj) {
        return true;
      }
      if (!(obj instanceof RexSubQuery)) {
        return false;
      }
      RexSubQuery sq = (RexSubQuery) obj;
      return op.equals(sq.op)
          && operands.equals(sq.operands)
          && rel.deepEquals(sq.rel);
    }
  
    @Override public int hashCode() {
      if (hash == 0) {
        hash = Objects.hash(op, operands, rel.deepHashCode());
      }
      return hash;
    }
  }
  