/* * 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; import org.apache.calcite.sql.validate.SqlMonotonicity; import java.util.Objects; import javax.annotation.Nonnull; /** * Definition of the ordering of one field of a {@link RelNode} whose * output is to be sorted. * * @see RelCollation */ public class RelFieldCollation { /** Utility method that compares values taking into account null * direction. */ public static int compare(Comparable c1, Comparable c2, int nullComparison) { if (c1 == c2) { return 0; } else if (c1 == null) { return nullComparison; } else if (c2 == null) { return -nullComparison; } else { //noinspection unchecked return c1.compareTo(c2); } } //~ Enums ------------------------------------------------------------------ /** * Direction that a field is ordered in. */ public enum Direction { /** * Ascending direction: A value is always followed by a greater or equal * value. */ ASCENDING("ASC"), /** * Strictly ascending direction: A value is always followed by a greater * value. */ STRICTLY_ASCENDING("SASC"), /** * Descending direction: A value is always followed by a lesser or equal * value. */ DESCENDING("DESC"), /** * Strictly descending direction: A value is always followed by a lesser * value. */ STRICTLY_DESCENDING("SDESC"), /** * Clustered direction: Values occur in no particular order, and the * same value may occur in contiguous groups, but never occurs after * that. This sort order tends to occur when values are ordered * according to a hash-key. */ CLUSTERED("CLU"); public final String shortString; Direction(String shortString) { this.shortString = shortString; } /** Converts the direction to a * {@link org.apache.calcite.sql.validate.SqlMonotonicity}. */ public SqlMonotonicity monotonicity() { switch (this) { case ASCENDING: return SqlMonotonicity.INCREASING; case STRICTLY_ASCENDING: return SqlMonotonicity.STRICTLY_INCREASING; case DESCENDING: return SqlMonotonicity.DECREASING; case STRICTLY_DESCENDING: return SqlMonotonicity.STRICTLY_DECREASING; case CLUSTERED: return SqlMonotonicity.MONOTONIC; default: throw new AssertionError("unknown: " + this); } } /** Converts a {@link SqlMonotonicity} to a direction. */ public static Direction of(SqlMonotonicity monotonicity) { switch (monotonicity) { case INCREASING: return ASCENDING; case DECREASING: return DESCENDING; case STRICTLY_INCREASING: return STRICTLY_ASCENDING; case STRICTLY_DECREASING: return STRICTLY_DESCENDING; case MONOTONIC: return CLUSTERED; default: throw new AssertionError("unknown: " + monotonicity); } } /** Returns the null direction if not specified. Consistent with Oracle, * NULLS are sorted as if they were positive infinity. */ public @Nonnull NullDirection defaultNullDirection() { switch (this) { case ASCENDING: case STRICTLY_ASCENDING: return NullDirection.LAST; case DESCENDING: case STRICTLY_DESCENDING: return NullDirection.FIRST; default: return NullDirection.UNSPECIFIED; } } /** Returns whether this is {@link #DESCENDING} or * {@link #STRICTLY_DESCENDING}. */ public boolean isDescending() { switch (this) { case DESCENDING: case STRICTLY_DESCENDING: return true; default: return false; } } } /** * Ordering of nulls. */ public enum NullDirection { FIRST(-1), LAST(1), UNSPECIFIED(1); public final int nullComparison; NullDirection(int nullComparison) { this.nullComparison = nullComparison; } } //~ Instance fields -------------------------------------------------------- /** * 0-based index of field being sorted. */ private final int fieldIndex; /** * Direction of sorting. */ public final Direction direction; /** * Direction of sorting of nulls. */ public final NullDirection nullDirection; //~ Constructors ----------------------------------------------------------- /** * Creates an ascending field collation. */ public RelFieldCollation(int fieldIndex) { this(fieldIndex, Direction.ASCENDING); } /** * Creates a field collation with unspecified null direction. */ public RelFieldCollation(int fieldIndex, Direction direction) { this(fieldIndex, direction, direction.defaultNullDirection()); } /** * Creates a field collation. */ public RelFieldCollation( int fieldIndex, Direction direction, NullDirection nullDirection) { this.fieldIndex = fieldIndex; this.direction = Objects.requireNonNull(direction); this.nullDirection = Objects.requireNonNull(nullDirection); } //~ Methods ---------------------------------------------------------------- /** * Creates a copy of this RelFieldCollation against a different field. */ public RelFieldCollation withFieldIndex(int fieldIndex) { return this.fieldIndex == fieldIndex ? this : new RelFieldCollation(fieldIndex, direction, nullDirection); } @Deprecated // to be removed before 2.0 public RelFieldCollation copy(int target) { return withFieldIndex(target); } /** Creates a copy of this RelFieldCollation with a different direction. */ public RelFieldCollation withDirection(Direction direction) { return this.direction == direction ? this : new RelFieldCollation(fieldIndex, direction, nullDirection); } /** Creates a copy of this RelFieldCollation with a different null * direction. */ public RelFieldCollation withNullDirection(NullDirection nullDirection) { return this.nullDirection == nullDirection ? this : new RelFieldCollation(fieldIndex, direction, nullDirection); } /** * Returns a copy of this RelFieldCollation with the field index shifted * {@code offset} to the right. */ public RelFieldCollation shift(int offset) { return withFieldIndex(fieldIndex + offset); } @Override public boolean equals(Object o) { return this == o || o instanceof RelFieldCollation && fieldIndex == ((RelFieldCollation) o).fieldIndex && direction == ((RelFieldCollation) o).direction && nullDirection == ((RelFieldCollation) o).nullDirection; } @Override public int hashCode() { return Objects.hash(fieldIndex, direction, nullDirection); } public int getFieldIndex() { return fieldIndex; } public RelFieldCollation.Direction getDirection() { return direction; } public String toString() { if (direction == Direction.ASCENDING && nullDirection == direction.defaultNullDirection()) { return String.valueOf(fieldIndex); } final StringBuilder sb = new StringBuilder(); sb.append(fieldIndex).append(" ").append(direction.shortString); if (nullDirection != direction.defaultNullDirection()) { sb.append(" ").append(nullDirection); } return sb.toString(); } public String shortString() { if (nullDirection == direction.defaultNullDirection()) { return direction.shortString; } switch (nullDirection) { case FIRST: return direction.shortString + "-nulls-first"; case LAST: return direction.shortString + "-nulls-last"; default: return direction.shortString; } } }