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.adapter.innodb;
  
  import org.apache.calcite.rel.RelCollation;
  import org.apache.calcite.rel.RelCollations;
  import org.apache.calcite.rel.RelFieldCollation;
  import org.apache.calcite.rex.RexNode;
  import org.apache.calcite.util.Pair;
  
  import com.alibaba.innodb.java.reader.comparator.ComparisonOperator;
  import com.google.common.collect.ImmutableList;
  
  import org.checkerframework.checker.nullness.qual.Nullable;
  
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Objects;
  
  import static com.google.common.base.Preconditions.checkState;
  
  import static java.util.Objects.requireNonNull;
  
  /**
   * Index condition.
   *
   * <p>Works in the following places:
   *
   * <ul>
   * <li>In {@link InnodbFilterTranslator}, it is the index condition
   * to push down according to {@link InnodbFilter} by planner rule.
   *
   * <li>In {@link InnodbTableScan}, it represents a full scan by a
   * primary key or a secondary key.
   *
   * <li>In code generation, it indicates the storage engine which index
   * to use and the associated condition if present.
   * </ul>
   */
  public class IndexCondition {
  
    static final IndexCondition EMPTY_CONDITION =
        create("", QueryType.PK_FULL_SCAN,
            null, ComparisonOperator.NOP, ComparisonOperator.NOP,
            ImmutableList.of(), ImmutableList.of());
  
    /** Field names per row type. */
    private final List<String> fieldNames;
    private final String indexName;
    private final List<String> indexColumnNames;
    private final RelCollation implicitCollation;
    private final List<RexNode> pushDownConditions;
    private final List<RexNode> remainderConditions;
  
    private final QueryType queryType;
    private final List<Object> pointQueryKey;
    private final ComparisonOperator rangeQueryLowerOp;
    private final ComparisonOperator rangeQueryUpperOp;
    private final List<Object> rangeQueryLowerKey;
    private final List<Object> rangeQueryUpperKey;
  
    /** Constructor that assigns all fields. All other constructors call this. */
    private IndexCondition(
        List<String> fieldNames,
        String indexName,
        List<String> indexColumnNames,
        @Nullable RelCollation implicitCollation,
        @Nullable List<RexNode> pushDownConditions,
        @Nullable List<RexNode> remainderConditions,
        QueryType queryType,
        @Nullable List<Object> pointQueryKey,
        ComparisonOperator rangeQueryLowerOp,
        ComparisonOperator rangeQueryUpperOp,
        List<Object> rangeQueryLowerKey,
        List<Object> rangeQueryUpperKey) {
      this.fieldNames = fieldNames;
      this.indexName = indexName;
      this.indexColumnNames = indexColumnNames;
      this.implicitCollation =
          implicitCollation != null ? implicitCollation
              : deduceImplicitCollation(fieldNames, indexColumnNames);
      this.pushDownConditions =
          pushDownConditions == null ? ImmutableList.of()
              : ImmutableList.copyOf(pushDownConditions);
      this.remainderConditions =
          remainderConditions == null ? ImmutableList.of()
              : ImmutableList.copyOf(remainderConditions);
      this.queryType = queryType;
      this.pointQueryKey =
          pointQueryKey == null ? ImmutableList.of()
              : ImmutableList.copyOf(pointQueryKey);
      this.rangeQueryLowerOp = requireNonNull(rangeQueryLowerOp, "rangeQueryLowerOp");
      this.rangeQueryUpperOp = requireNonNull(rangeQueryUpperOp, "rangeQueryUpperOp");
      this.rangeQueryLowerKey = ImmutableList.copyOf(rangeQueryLowerKey);
      this.rangeQueryUpperKey = ImmutableList.copyOf(rangeQueryUpperKey);
    }
  
    static IndexCondition create(
        List<String> fieldNames,
        String indexName,
        List<String> indexColumnNames,
        QueryType queryType) {
      return new IndexCondition(fieldNames, indexName, indexColumnNames, null,
          null, null, queryType, null, ComparisonOperator.NOP,
          ComparisonOperator.NOP, ImmutableList.of(), ImmutableList.of());
    }
  
    /**
     * Creates a new instance for {@link InnodbFilterTranslator} to build
     * index condition which can be pushed down.
     */
    static IndexCondition create(
        List<String> fieldNames,
        String indexName,
        List<String> indexColumnNames,
        List<RexNode> pushDownConditions,
        List<RexNode> remainderConditions) {
      return new IndexCondition(fieldNames, indexName, indexColumnNames, null,
          pushDownConditions, remainderConditions, QueryType.PK_FULL_SCAN, null,
          ComparisonOperator.NOP, ComparisonOperator.NOP, ImmutableList.of(),
          ImmutableList.of());
    }
  
    /**
     * Creates a new instance for code generation to build query parameters
     * for underlying storage engine <code>Innodb-java-reader</code>.
     */
    public static IndexCondition create(
        String indexName,
        QueryType queryType,
        @Nullable List<Object> pointQueryKey,
        ComparisonOperator rangeQueryLowerOp,
        ComparisonOperator rangeQueryUpperOp,
        List<Object> rangeQueryLowerKey,
        List<Object> rangeQueryUpperKey) {
      return new IndexCondition(ImmutableList.of(), indexName, ImmutableList.of(),
          null, null, null, queryType, pointQueryKey, rangeQueryLowerOp,
          rangeQueryUpperOp, rangeQueryLowerKey, rangeQueryUpperKey);
    }
  
    /** Returns whether there are any push down conditions. */
    boolean canPushDown() {
      return !pushDownConditions.isEmpty();
    }
  
    public RelCollation getImplicitCollation() {
      return implicitCollation;
    }
  
    /**
     * Infers the implicit correlation from the index.
     *
     * @param indexColumnNames index column names
     * @return the collation of the filtered results
     */
    private static RelCollation deduceImplicitCollation(List<String> fieldNames,
        List<String> indexColumnNames) {
      requireNonNull(fieldNames, "field names must not be null");
      List<RelFieldCollation> keyCollations = new ArrayList<>(indexColumnNames.size());
      for (String keyColumnName : indexColumnNames) {
        int fieldIndex = fieldNames.indexOf(keyColumnName);
        keyCollations.add(
            new RelFieldCollation(fieldIndex, RelFieldCollation.Direction.ASCENDING));
      }
      return RelCollations.of(keyCollations);
    }
  
    public IndexCondition withFieldNames(List<String> fieldNames) {
      if (Objects.equals(fieldNames, this.fieldNames)) {
        return this;
      }
      return new IndexCondition(fieldNames, indexName, indexColumnNames,
          implicitCollation, pushDownConditions, remainderConditions,
          queryType, pointQueryKey, rangeQueryLowerOp, rangeQueryUpperOp,
          rangeQueryLowerKey, rangeQueryUpperKey);
    }
  
    public String getIndexName() {
      return indexName;
    }
  
    public IndexCondition withIndexName(String indexName) {
      if (Objects.equals(indexName, this.indexName)) {
        return this;
      }
      return new IndexCondition(fieldNames, indexName, indexColumnNames,
          implicitCollation, pushDownConditions, remainderConditions,
          queryType, pointQueryKey, rangeQueryLowerOp, rangeQueryUpperOp,
          rangeQueryLowerKey, rangeQueryUpperKey);
    }
  
    public IndexCondition withIndexColumnNames(List<String> indexColumnNames) {
      if (Objects.equals(indexColumnNames, this.indexColumnNames)) {
        return this;
      }
      return new IndexCondition(fieldNames, indexName, indexColumnNames,
          implicitCollation, pushDownConditions, remainderConditions,
          queryType, pointQueryKey, rangeQueryLowerOp, rangeQueryUpperOp,
          rangeQueryLowerKey, rangeQueryUpperKey);
    }
  
    public List<RexNode> getPushDownConditions() {
      return pushDownConditions;
    }
  
    public IndexCondition withPushDownConditions(List<RexNode> pushDownConditions) {
      if (Objects.equals(pushDownConditions, this.pushDownConditions)) {
        return this;
      }
      return new IndexCondition(fieldNames, indexName, indexColumnNames,
          implicitCollation, pushDownConditions, remainderConditions,
          queryType, pointQueryKey, rangeQueryLowerOp, rangeQueryUpperOp,
          rangeQueryLowerKey, rangeQueryUpperKey);
    }
  
    public List<RexNode> getRemainderConditions() {
      return remainderConditions;
    }
  
    public IndexCondition withRemainderConditions(List<RexNode> remainderConditions) {
      if (Objects.equals(remainderConditions, this.remainderConditions)) {
        return this;
      }
      return new IndexCondition(fieldNames, indexName, indexColumnNames,
          implicitCollation, pushDownConditions, remainderConditions,
          queryType, pointQueryKey, rangeQueryLowerOp, rangeQueryUpperOp,
          rangeQueryLowerKey, rangeQueryUpperKey);
    }
  
    public QueryType getQueryType() {
      return queryType;
    }
  
    public IndexCondition withQueryType(QueryType queryType) {
      if (queryType == this.queryType) {
        return this;
      }
      return new IndexCondition(fieldNames, indexName, indexColumnNames,
          implicitCollation, pushDownConditions, remainderConditions,
          queryType, pointQueryKey, rangeQueryLowerOp, rangeQueryUpperOp,
          rangeQueryLowerKey, rangeQueryUpperKey);
    }
  
    public List<Object> getPointQueryKey() {
      return pointQueryKey;
    }
  
    public IndexCondition withPointQueryKey(List<Object> pointQueryKey) {
      if (pointQueryKey == this.pointQueryKey) {
        return this;
      }
      return new IndexCondition(fieldNames, indexName, indexColumnNames,
          implicitCollation, pushDownConditions, remainderConditions,
          queryType, pointQueryKey, rangeQueryLowerOp, rangeQueryUpperOp,
          rangeQueryLowerKey, rangeQueryUpperKey);
    }
  
    public ComparisonOperator getRangeQueryLowerOp() {
      return rangeQueryLowerOp;
    }
  
    public IndexCondition withRangeQueryLowerOp(ComparisonOperator rangeQueryLowerOp) {
      if (rangeQueryLowerOp == this.rangeQueryLowerOp) {
        return this;
      }
      return new IndexCondition(fieldNames, indexName, indexColumnNames,
          implicitCollation, pushDownConditions, remainderConditions,
          queryType, pointQueryKey, rangeQueryLowerOp, rangeQueryUpperOp,
          rangeQueryLowerKey, rangeQueryUpperKey);
    }
  
    public ComparisonOperator getRangeQueryUpperOp() {
      return rangeQueryUpperOp;
    }
  
    public IndexCondition withRangeQueryUpperOp(ComparisonOperator rangeQueryUpperOp) {
      if (rangeQueryUpperOp == this.rangeQueryUpperOp) {
        return this;
      }
      return new IndexCondition(fieldNames, indexName, indexColumnNames,
          implicitCollation, pushDownConditions, remainderConditions,
          queryType, pointQueryKey, rangeQueryLowerOp, rangeQueryUpperOp,
          rangeQueryLowerKey, rangeQueryUpperKey);
    }
  
    public List<Object> getRangeQueryLowerKey() {
      return rangeQueryLowerKey;
    }
  
    public IndexCondition withRangeQueryLowerKey(List<Object> rangeQueryLowerKey) {
      if (rangeQueryLowerKey == this.rangeQueryLowerKey) {
        return this;
      }
      return new IndexCondition(fieldNames, indexName, indexColumnNames,
          implicitCollation, pushDownConditions, remainderConditions,
          queryType, pointQueryKey, rangeQueryLowerOp, rangeQueryUpperOp,
          rangeQueryLowerKey, rangeQueryUpperKey);
    }
  
    public List<Object> getRangeQueryUpperKey() {
      return rangeQueryUpperKey;
    }
  
    public IndexCondition withRangeQueryUpperKey(List<Object> rangeQueryUpperKey) {
      if (rangeQueryUpperKey == this.rangeQueryUpperKey) {
        return this;
      }
      return new IndexCondition(fieldNames, indexName, indexColumnNames,
          implicitCollation, pushDownConditions, remainderConditions,
          queryType, pointQueryKey, rangeQueryLowerOp, rangeQueryUpperOp,
          rangeQueryLowerKey, rangeQueryUpperKey);
    }
  
    public boolean nameMatch(String name) {
      return name != null && name.equalsIgnoreCase(indexName);
    }
  
    @Override public String toString() {
      final StringBuilder builder = new StringBuilder("(");
      builder.append(queryType).append(", index=").append(indexName);
      if (queryType == QueryType.PK_POINT_QUERY
          || queryType == QueryType.SK_POINT_QUERY) {
        checkState(pointQueryKey.size() == indexColumnNames.size());
        append(builder, indexColumnNames, pointQueryKey, "=");
      } else {
        if (!rangeQueryLowerKey.isEmpty()) {
          append(builder, indexColumnNames, rangeQueryLowerKey, rangeQueryLowerOp.value());
        }
        if (!rangeQueryUpperKey.isEmpty()) {
          append(builder, indexColumnNames, rangeQueryUpperKey, rangeQueryUpperOp.value());
        }
      }
      builder.append(")");
      return builder.toString();
    }
  
    private static void append(StringBuilder builder, List<String> keyColumnNames,
        List<Object> key, String op) {
      builder.append(", ");
      for (Pair<String, Object> value : Pair.zip(keyColumnNames, key)) {
        builder.append(value.getKey());
        builder.append(op);
        builder.append(value.getValue());
        builder.append(",");
      }
      builder.deleteCharAt(builder.length() - 1);
    }
  }
  