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.rel.rules;
  
  import org.apache.calcite.plan.RelOptPredicateList;
  import org.apache.calcite.plan.RelOptRuleCall;
  import org.apache.calcite.plan.RelOptRuleOperand;
  import org.apache.calcite.plan.RelRule;
  import org.apache.calcite.rel.RelNode;
  import org.apache.calcite.rel.core.Values;
  import org.apache.calcite.rel.logical.LogicalFilter;
  import org.apache.calcite.rel.logical.LogicalProject;
  import org.apache.calcite.rel.logical.LogicalValues;
  import org.apache.calcite.rel.type.RelDataType;
  import org.apache.calcite.rex.RexBuilder;
  import org.apache.calcite.rex.RexInputRef;
  import org.apache.calcite.rex.RexLiteral;
  import org.apache.calcite.rex.RexNode;
  import org.apache.calcite.rex.RexShuttle;
  import org.apache.calcite.rex.RexUtil;
  import org.apache.calcite.tools.RelBuilderFactory;
  import org.apache.calcite.util.Util;
  import org.apache.calcite.util.trace.CalciteTrace;
  
  import com.google.common.collect.ImmutableList;
  
  import org.checkerframework.checker.nullness.qual.Nullable;
  import org.immutables.value.Value;
  import org.slf4j.Logger;
  
  import java.util.ArrayList;
  import java.util.List;
  
  import static com.google.common.base.Preconditions.checkArgument;
  
  import static java.util.Objects.requireNonNull;
  
  /**
   * Planner rule that folds projections and filters into an underlying
   * {@link org.apache.calcite.rel.logical.LogicalValues}.
   *
   * <p>Returns a simplified {@code Values}, perhaps containing zero tuples
   * if all rows are filtered away.
   *
   * <p>For example,
   *
   * <blockquote><code>select a - b from (values (1, 2), (3, 5), (7, 11)) as t (a,
   * b) where a + b &gt; 4</code></blockquote>
   *
   * <p>becomes
   *
   * <blockquote><code>select x from (values (-2), (-4))</code></blockquote>
   *
   * <p>Ignores an empty {@code Values}; this is better dealt with by
   * {@link PruneEmptyRules}.
   *
   * @see CoreRules#FILTER_VALUES_MERGE
   * @see CoreRules#PROJECT_VALUES_MERGE
   * @see CoreRules#PROJECT_FILTER_VALUES_MERGE
   */
  @Value.Enclosing
  public class ValuesReduceRule
      extends RelRule<ValuesReduceRule.Config>
      implements TransformationRule {
  
    private static final Logger LOGGER = CalciteTrace.getPlannerTracer();
  
    /** Creates a ValuesReduceRule. */
    protected ValuesReduceRule(Config config) {
      super(config);
      Util.discard(LOGGER);
    }
  
    @Deprecated // to be removed before 2.0
    public ValuesReduceRule(RelOptRuleOperand operand,
        RelBuilderFactory relBuilderFactory, String desc) {
      this(ImmutableValuesReduceRule.Config.builder().withRelBuilderFactory(relBuilderFactory)
          .withDescription(desc)
          .withOperandSupplier(b -> b.exactly(operand))
          .withMatchHandler((u, v) -> {
            throw new IllegalArgumentException("Match handler not set.");
          })
          .build());
      throw new IllegalArgumentException("cannot guess matchHandler");
    }
  
    private static void matchProjectFilter(ValuesReduceRule rule,
        RelOptRuleCall call) {
      LogicalProject project = call.rel(0);
      LogicalFilter filter = call.rel(1);
      LogicalValues values = call.rel(2);
      rule.apply(call, project, filter, values);
    }
  
    private static void matchProject(ValuesReduceRule rule, RelOptRuleCall call) {
      LogicalProject project = call.rel(0);
      LogicalValues values = call.rel(1);
      rule.apply(call, project, null, values);
    }
  
    private static void matchFilter(ValuesReduceRule rule, RelOptRuleCall call) {
      LogicalFilter filter = call.rel(0);
      LogicalValues values = call.rel(1);
      rule.apply(call, null, filter, values);
    }
  
    //~ Methods ----------------------------------------------------------------
  
    @Override public void onMatch(RelOptRuleCall call) {
      config.matchHandler().accept(this, call);
    }
  
    /**
     * Does the work.
     *
     * @param call    Rule call
     * @param project Project, may be null
     * @param filter  Filter, may be null
     * @param values  Values rel to be reduced
     */
    protected void apply(RelOptRuleCall call, @Nullable LogicalProject project,
        @Nullable LogicalFilter filter, LogicalValues values) {
      requireNonNull(values, "values");
      checkArgument(filter != null || project != null);
      final RexNode conditionExpr =
          (filter == null) ? null : filter.getCondition();
      final List<RexNode> projectExprs =
          (project == null) ? null : project.getProjects();
      RexBuilder rexBuilder = values.getCluster().getRexBuilder();
  
      // Find reducible expressions.
      final List<RexNode> reducibleExps = new ArrayList<>();
      final MyRexShuttle shuttle = new MyRexShuttle();
      for (final List<RexLiteral> literalList : values.getTuples()) {
        shuttle.literalList = literalList;
        if (conditionExpr != null) {
          RexNode c = conditionExpr.accept(shuttle);
          reducibleExps.add(c);
        }
        if (projectExprs != null) {
          requireNonNull(project, "project");
          int k = -1;
          for (RexNode projectExpr : projectExprs) {
            ++k;
            RexNode e = projectExpr.accept(shuttle);
            if (RexLiteral.isNullLiteral(e)) {
              RelDataType type =
                  project.getRowType().getFieldList().get(k).getType();
              e = rexBuilder.makeAbstractCast(type, e, false);
            }
            reducibleExps.add(e);
          }
        }
      }
      int fieldsPerRow =
          ((conditionExpr == null) ? 0 : 1)
              + ((projectExprs == null) ? 0 : projectExprs.size());
      assert fieldsPerRow > 0;
      assert reducibleExps.size() == (values.getTuples().size() * fieldsPerRow);
  
      // Compute the values they reduce to.
      final RelOptPredicateList predicates = RelOptPredicateList.EMPTY;
      ReduceExpressionsRule.reduceExpressions(values, reducibleExps, predicates,
          false, true, false);
  
      int changeCount = 0;
      final ImmutableList.Builder<ImmutableList<RexLiteral>> tuplesBuilder =
          ImmutableList.builder();
      for (int row = 0; row < values.getTuples().size(); ++row) {
        int i = 0;
        if (conditionExpr != null) {
          final RexNode reducedValue =
              reducibleExps.get((row * fieldsPerRow) + i);
          ++i;
          if (!reducedValue.isAlwaysTrue()) {
            ++changeCount;
            continue;
          }
        }
  
        final ImmutableList<RexLiteral> valuesList;
        if (projectExprs != null) {
          ++changeCount;
          final ImmutableList.Builder<RexLiteral> tupleBuilder =
              ImmutableList.builder();
          for (; i < fieldsPerRow; ++i) {
            final RexNode reducedValue =
                reducibleExps.get((row * fieldsPerRow) + i);
            if (reducedValue instanceof RexLiteral) {
              tupleBuilder.add((RexLiteral) reducedValue);
            } else if (RexUtil.isNullLiteral(reducedValue, true)) {
              tupleBuilder.add(rexBuilder.makeNullLiteral(reducedValue.getType()));
            } else {
              return;
            }
          }
          valuesList = tupleBuilder.build();
        } else {
          valuesList = values.getTuples().get(row);
        }
        tuplesBuilder.add(valuesList);
      }
  
      if (changeCount > 0) {
        final RelDataType rowType;
        if (projectExprs != null) {
          rowType = requireNonNull(project, "project").getRowType();
        } else {
          rowType = values.getRowType();
        }
        final RelNode newRel =
            LogicalValues.create(values.getCluster(), rowType,
                tuplesBuilder.build());
        call.transformTo(newRel);
      } else {
        // Filter had no effect, so we can say that Filter(Values) ==
        // Values.
        call.transformTo(values);
      }
  
      // New plan is absolutely better than old plan. (Moreover, if
      // changeCount == 0, we've proved that the filter was trivial, and that
      // can send the volcano planner into a loop; see dtbug 2070.)
      if (filter != null) {
        call.getPlanner().prune(filter);
      }
    }
  
    //~ Inner Classes ----------------------------------------------------------
  
    /** Shuttle that converts inputs to literals. */
    private static class MyRexShuttle extends RexShuttle {
      private @Nullable List<RexLiteral> literalList;
  
      @Override public RexNode visitInputRef(RexInputRef inputRef) {
        requireNonNull(literalList, "literalList");
        return literalList.get(inputRef.getIndex());
      }
    }
  
    /** Rule configuration. */
    @Value.Immutable(singleton = false)
    public interface Config extends RelRule.Config {
      Config FILTER = ImmutableValuesReduceRule.Config.builder()
          .withDescription("ValuesReduceRule(Filter)")
          .withOperandSupplier(b0 ->
              b0.operand(LogicalFilter.class).oneInput(b1 ->
                  b1.operand(LogicalValues.class)
                      .predicate(Values::isNotEmpty).noInputs()))
          .withMatchHandler(ValuesReduceRule::matchFilter)
          .build();
  
      Config PROJECT = ImmutableValuesReduceRule.Config.builder()
          .withDescription("ValuesReduceRule(Project)")
          .withOperandSupplier(b0 ->
              b0.operand(LogicalProject.class).oneInput(b1 ->
                  b1.operand(LogicalValues.class)
                      .predicate(Values::isNotEmpty).noInputs()))
          .withMatchHandler(ValuesReduceRule::matchProject)
          .build();
  
      Config PROJECT_FILTER = ImmutableValuesReduceRule.Config.builder()
          .withDescription("ValuesReduceRule(Project-Filter)")
          .withOperandSupplier(b0 ->
              b0.operand(LogicalProject.class).oneInput(b1 ->
                  b1.operand(LogicalFilter.class).oneInput(b2 ->
                      b2.operand(LogicalValues.class)
                          .predicate(Values::isNotEmpty).noInputs())))
          .withMatchHandler(ValuesReduceRule::matchProjectFilter)
          .build();
  
      @Override default ValuesReduceRule toRule() {
        return new ValuesReduceRule(this);
      }
  
      /** Forwards a call to {@link #onMatch(RelOptRuleCall)}. */
      @Value.Parameter
      MatchHandler<ValuesReduceRule> matchHandler();
  
      /** Sets {@link #matchHandler()}. */
      Config withMatchHandler(MatchHandler<ValuesReduceRule> matchHandler);
  
      /** Defines an operand tree for the given classes. */
      default Config withOperandFor(Class<? extends RelNode> relClass) {
        return withOperandSupplier(b -> b.operand(relClass).anyInputs())
            .as(Config.class);
      }
    }
  
  }
  