blob: 8707b21312dd63686062e8a47dd3e65c5b874447 [file] [log] [blame]
/*
* 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 org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Objects;
/**
* 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(@Nullable Comparable c1, @Nullable 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 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;
}
}
/**
* Returns the reverse of this direction.
*
* @return reverse of the input direction
*/
public Direction reverse() {
switch (this) {
case ASCENDING:
return DESCENDING;
case STRICTLY_ASCENDING:
return STRICTLY_DESCENDING;
case DESCENDING:
return ASCENDING;
case STRICTLY_DESCENDING:
return STRICTLY_ASCENDING;
default:
return this;
}
}
/** Removes strictness. */
public Direction lax() {
switch (this) {
case STRICTLY_ASCENDING:
return ASCENDING;
case STRICTLY_DESCENDING:
return DESCENDING;
default:
return this;
}
}
}
/**
* 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, "direction");
this.nullDirection = Objects.requireNonNull(nullDirection, "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(@Nullable 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;
}
@Override 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;
}
}
}