/**
 * RelMdColumnUniqueness supplies a default implementation of {@link
 * RelMetadataQuery#areColumnsUnique} for the standard logical algebra.
 */
public class RelMdColumnUniqueness {
  public static final RelMetadataProvider SOURCE =
      ReflectiveRelMetadataProvider.reflectiveSource(
          BuiltinMethod.COLUMN_UNIQUENESS.method, new RelMdColumnUniqueness());

  // ~ Constructors -----------------------------------------------------------

  private RelMdColumnUniqueness() {}

  // ~ Methods ----------------------------------------------------------------

  public Boolean areColumnsUnique(FilterRelBase rel, BitSet columns, boolean ignoreNulls) {
    return RelMetadataQuery.areColumnsUnique(rel.getChild(), columns, ignoreNulls);
  }

  public Boolean areColumnsUnique(SortRel rel, BitSet columns, boolean ignoreNulls) {
    return RelMetadataQuery.areColumnsUnique(rel.getChild(), columns, ignoreNulls);
  }

  public Boolean areColumnsUnique(CorrelatorRel rel, BitSet columns, boolean ignoreNulls) {
    return RelMetadataQuery.areColumnsUnique(rel.getLeft(), columns, ignoreNulls);
  }

  public Boolean areColumnsUnique(ProjectRelBase rel, BitSet columns, boolean ignoreNulls) {
    // ProjectRel maps a set of rows to a different set;
    // Without knowledge of the mapping function(whether it
    // preserves uniqueness), it is only safe to derive uniqueness
    // info from the child of a project when the mapping is f(a) => a.
    //
    // Also need to map the input column set to the corresponding child
    // references

    List<RexNode> projExprs = rel.getProjects();
    BitSet childColumns = new BitSet();
    for (int bit : BitSets.toIter(columns)) {
      RexNode projExpr = projExprs.get(bit);
      if (projExpr instanceof RexInputRef) {
        childColumns.set(((RexInputRef) projExpr).getIndex());
      } else if (projExpr instanceof RexCall && ignoreNulls) {
        // If the expression is a cast such that the types are the same
        // except for the nullability, then if we're ignoring nulls,
        // it doesn't matter whether the underlying column reference
        // is nullable.  Check that the types are the same by making a
        // nullable copy of both types and then comparing them.
        RexCall call = (RexCall) projExpr;
        if (call.getOperator() != SqlStdOperatorTable.CAST) {
          continue;
        }
        RexNode castOperand = call.getOperands().get(0);
        if (!(castOperand instanceof RexInputRef)) {
          continue;
        }
        RelDataTypeFactory typeFactory = rel.getCluster().getTypeFactory();
        RelDataType castType = typeFactory.createTypeWithNullability(projExpr.getType(), true);
        RelDataType origType = typeFactory.createTypeWithNullability(castOperand.getType(), true);
        if (castType.equals(origType)) {
          childColumns.set(((RexInputRef) castOperand).getIndex());
        }
      } else {
        // If the expression will not influence uniqueness of the
        // projection, then skip it.
        continue;
      }
    }

    // If no columns can affect uniqueness, then return unknown
    if (childColumns.cardinality() == 0) {
      return null;
    }

    return RelMetadataQuery.areColumnsUnique(rel.getChild(), childColumns, ignoreNulls);
  }

  public Boolean areColumnsUnique(JoinRelBase rel, BitSet columns, boolean ignoreNulls) {
    if (columns.cardinality() == 0) {
      return false;
    }

    final RelNode left = rel.getLeft();
    final RelNode right = rel.getRight();

    // Divide up the input column mask into column masks for the left and
    // right sides of the join
    BitSet leftColumns = new BitSet();
    BitSet rightColumns = new BitSet();
    int nLeftColumns = left.getRowType().getFieldCount();
    for (int bit : BitSets.toIter(columns)) {
      if (bit < nLeftColumns) {
        leftColumns.set(bit);
      } else {
        rightColumns.set(bit - nLeftColumns);
      }
    }

    // If the original column mask contains columns from both the left and
    // right hand side, then the columns are unique if and only if they're
    // unique for their respective join inputs
    Boolean leftUnique = RelMetadataQuery.areColumnsUnique(left, leftColumns, ignoreNulls);
    Boolean rightUnique = RelMetadataQuery.areColumnsUnique(right, rightColumns, ignoreNulls);
    if ((leftColumns.cardinality() > 0) && (rightColumns.cardinality() > 0)) {
      if ((leftUnique == null) || (rightUnique == null)) {
        return null;
      } else {
        return leftUnique && rightUnique;
      }
    }

    // If we're only trying to determine uniqueness for columns that
    // originate from one join input, then determine if the equijoin
    // columns from the other join input are unique.  If they are, then
    // the columns are unique for the entire join if they're unique for
    // the corresponding join input, provided that input is not null
    // generating.
    final JoinInfo joinInfo = rel.analyzeCondition();
    if (leftColumns.cardinality() > 0) {
      if (rel.getJoinType().generatesNullsOnLeft()) {
        return false;
      }
      Boolean rightJoinColsUnique =
          RelMetadataQuery.areColumnsUnique(right, joinInfo.rightSet(), ignoreNulls);
      if ((rightJoinColsUnique == null) || (leftUnique == null)) {
        return null;
      }
      return rightJoinColsUnique && leftUnique;
    } else if (rightColumns.cardinality() > 0) {
      if (rel.getJoinType().generatesNullsOnRight()) {
        return false;
      }
      Boolean leftJoinColsUnique =
          RelMetadataQuery.areColumnsUnique(left, joinInfo.leftSet(), ignoreNulls);
      if ((leftJoinColsUnique == null) || (rightUnique == null)) {
        return null;
      }
      return leftJoinColsUnique && rightUnique;
    }

    throw new AssertionError();
  }

  public Boolean areColumnsUnique(SemiJoinRel rel, BitSet columns, boolean ignoreNulls) {
    // only return the unique keys from the LHS since a semijoin only
    // returns the LHS
    return RelMetadataQuery.areColumnsUnique(rel.getLeft(), columns, ignoreNulls);
  }

  public Boolean areColumnsUnique(AggregateRelBase rel, BitSet columns, boolean ignoreNulls) {
    // group by keys form a unique key
    if (rel.getGroupCount() > 0) {
      BitSet groupKey = new BitSet();
      for (int i = 0; i < rel.getGroupCount(); i++) {
        groupKey.set(i);
      }
      return BitSets.contains(columns, groupKey);
    } else {
      // interpret an empty set as asking whether the aggregation is full
      // table (in which case it returns at most one row);
      // TODO jvs 1-Sept-2008:  apply this convention consistently
      // to other relational expressions, as well as to
      // RelMetadataQuery.getUniqueKeys
      return columns.isEmpty();
    }
  }

  // Catch-all rule when none of the others apply.
  public Boolean areColumnsUnique(RelNode rel, BitSet columns, boolean ignoreNulls) {
    // no information available
    return null;
  }
}