/* * 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.eigenbase.sarg; import java.math.*; import org.eigenbase.reltype.*; import org.eigenbase.rex.*; import org.eigenbase.sql.type.*; import org.eigenbase.util.*; import net.hydromatic.avatica.ByteString; /** * SargEndpoint represents an endpoint of a ({@link SargInterval}). * * <p>Instances of SargEndpoint are immutable from outside this package. * Subclass {@link SargMutableEndpoint} is provided for manipulation from * outside the package. */ public class SargEndpoint implements Comparable<SargEndpoint> { // TODO jvs 16-Jan-2006: special pattern prefix support for LIKE operator //~ Instance fields -------------------------------------------------------- /** * Factory which produced this endpoint. */ protected final SargFactory factory; /** * Datatype for endpoint value. */ protected final RelDataType dataType; /** * Coordinate for this endpoint, constrained to be either {@link * RexLiteral}, {@link RexInputRef}, {@link RexDynamicParam}, or null to * represent infinity (positive or negative infinity is implied by * boundType). */ protected RexNode coordinate; /** * @see #getBoundType */ protected SargBoundType boundType; /** * @see #getStrictness */ protected SargStrictness strictness; //~ Constructors ----------------------------------------------------------- /** * @see SargFactory#newEndpoint */ SargEndpoint(SargFactory factory, RelDataType dataType) { this.factory = factory; this.dataType = dataType; boundType = SargBoundType.LOWER; strictness = SargStrictness.OPEN; } //~ Methods ---------------------------------------------------------------- void copyFrom(SargEndpoint other) { assert getDataType() == other.getDataType(); if (other.isFinite()) { setFinite( other.getBoundType(), other.getStrictness(), other.getCoordinate()); } else { setInfinity(other.getInfinitude()); } } /** * Sets this endpoint to either negative or positive infinity. An infinite * endpoint implies an open bound (negative infinity implies a lower bound, * while positive infinity implies an upper bound). * * @param infinitude either -1 or +1 */ void setInfinity(int infinitude) { assert (infinitude == -1) || (infinitude == 1); if (infinitude == -1) { boundType = SargBoundType.LOWER; } else { boundType = SargBoundType.UPPER; } strictness = SargStrictness.OPEN; coordinate = null; } /** * Sets a finite value for this endpoint. * * @param boundType bound type (upper/lower) * @param strictness boundary strictness * @param coordinate endpoint position */ void setFinite( SargBoundType boundType, SargStrictness strictness, RexNode coordinate) { // validate the input assert coordinate != null; if (!(coordinate instanceof RexDynamicParam) && !(coordinate instanceof RexInputRef)) { assert coordinate instanceof RexLiteral; RexLiteral literal = (RexLiteral) coordinate; if (!RexLiteral.isNullLiteral(literal)) { assert SqlTypeUtil.canAssignFrom( dataType, literal.getType()); } } this.boundType = boundType; this.coordinate = coordinate; this.strictness = strictness; convertToTargetType(); } private void convertToTargetType() { if (!(coordinate instanceof RexLiteral)) { // Dynamic parameters and RexInputRefs are always cast to the // target type before comparison, so they are guaranteed not to // need any special conversion logic. return; } RexLiteral literal = (RexLiteral) coordinate; if (RexLiteral.isNullLiteral(literal)) { return; } Comparable value = literal.getValue(); int roundingCompensation; if (value instanceof BigDecimal) { roundingCompensation = convertNumber((BigDecimal) value); } else if (value instanceof NlsString) { roundingCompensation = convertString((NlsString) value); } else if (value instanceof ByteString) { roundingCompensation = convertBytes((ByteString) value); } else { // NOTE jvs 19-Feb-2006: Once we support fractional time // precision, need to handle that here. return; } // rounding takes precedence over the strictness flag. // Input round strictness output effective strictness // >5.9 down -1 >=6 0 // >=5.9 down 0 >=6 0 // >6.1 up -1 >6 1 // >=6.1 up 0 >6 1 // <6.1 up 1 <=6 0 // <=6.1 up 0 <=6 0 // <5.9 down 1 <6 -1 // <=5.9 down 0 <6 -1 if (roundingCompensation == 0) { return; } if (boundType == SargBoundType.LOWER) { if (roundingCompensation < 0) { strictness = SargStrictness.CLOSED; } else { strictness = SargStrictness.OPEN; } } else if (boundType == SargBoundType.UPPER) { if (roundingCompensation > 0) { strictness = SargStrictness.CLOSED; } else { strictness = SargStrictness.OPEN; } } } private int convertString(NlsString value) { // For character strings, have to deal with truncation (complicated by // padding rules). boolean fixed = dataType.getSqlTypeName() == SqlTypeName.CHAR; String s = value.getValue(); String trimmed = Util.rtrim(s); if (fixed) { // For CHAR, canonical representation is padded. This is // required during execution of comparisons. s = Util.rpad(s, dataType.getPrecision()); } else { // For PAD SPACE, we can use trimmed representation as // canonical for VARCHAR. This may shave off cycles // during execution of comparisons. If we ever support the NO PAD // attribute, we'll have to do something different for collations // with that attribute enabled. s = trimmed; } // Truncate if needed if (s.length() > dataType.getPrecision()) { s = s.substring(0, dataType.getPrecision()); // Post-truncation, need to trim again if truncation // left spaces on the end if (!fixed) { s = Util.rtrim(s); } } coordinate = factory.getRexBuilder().makeCharLiteral( new NlsString( s, value.getCharsetName(), value.getCollation())); if (trimmed.length() > dataType.getPrecision()) { // Truncation is always "down" in coordinate space, so rounding // compensation is always "up". Note that this calculation is // intentionally with respect to the pre-truncation trim (not the // post-truncation trim) because that's what tells us whether // we truncated anything of significance. return 1; } else { // For PAD SPACE, trailing spaces have no effect on comparison, // so it's the same as if no truncation took place. return 0; } } private int convertBytes(ByteString value) { // REVIEW jvs 11-Sept-2006: What about 0-padding for BINARY? // For binary strings, have to deal with truncation. if (value.length() <= dataType.getPrecision()) { // No truncation required. return 0; } ByteString truncated = value.substring(0, dataType.getPrecision()); coordinate = factory.getRexBuilder().makeBinaryLiteral(truncated); // Truncation is always "down" in coordinate space, so rounding // compensation is always "up". return 1; } private int convertNumber(BigDecimal value) { if (SqlTypeUtil.isApproximateNumeric(dataType)) { // REVIEW jvs 18-Jan-2006: is it necessary to do anything for // approx types here? Wait until someone complains. May at least // have to deal with case of double->float overflow. return 0; } // For exact numerics, we have to deal with rounding/overflow fun and // games. // REVIEW jvs 19-Feb-2006: Why do we make things complicated with // RoundingMode.HALF_UP? We could just use RoundingMode.FLOOR so the // compensation direction would always be the same. Maybe because this // code was ported from Broadbase, where the conversion library didn't // support multiple rounding modes. BigDecimal roundedValue = value.setScale( dataType.getScale(), RoundingMode.HALF_UP); if (roundedValue.precision() > dataType.getPrecision()) { // Overflow. Convert to infinity. Note that this may // flip the bound type, which isn't really correct, // but we handle that outside in SargIntervalExpr.evaluate. setInfinity(roundedValue.signum()); return 0; } coordinate = factory.getRexBuilder().makeExactLiteral( roundedValue, dataType); // The sign of roundingCompensation should be the opposite of the // rounding direction, so subtract post-rounding value from // pre-rounding. return value.compareTo(roundedValue); } /** * @return true if this endpoint represents a closed (exact) bound; false if * open (strict) */ public boolean isClosed() { return strictness == SargStrictness.CLOSED; } /** * @return opposite of isClosed */ public boolean isOpen() { return strictness == SargStrictness.OPEN; } /** * @return false if this endpoint represents infinity (either positive or * negative); true if a finite coordinate */ public boolean isFinite() { return coordinate != null; } /** * @return -1 for negative infinity, +1 for positive infinity, 0 for a * finite endpoint */ public int getInfinitude() { if (!isFinite()) { if (boundType == SargBoundType.LOWER) { return -1; } else { return 1; } } else { return 0; } } /** * @return coordinate of this endpoint */ public RexNode getCoordinate() { return coordinate; } /** * @return true if this endpoint has the null value for its coordinate */ public boolean isNull() { if (!isFinite()) { return false; } return RexLiteral.isNullLiteral(coordinate); } /** * @return target datatype for coordinate */ public RelDataType getDataType() { return dataType; } /** * @return boundary type this endpoint represents */ public SargBoundType getBoundType() { return boundType; } /** * Tests whether this endpoint "touches" another one (not necessarily * overlapping). For example, the upper bound of the interval (1, 10) * touches the lower bound of the interval [10, 20), but not of the interval * (10, 20). * * @param other the other endpoint to test * @return true if touching; false if discontinuous */ public boolean isTouching(SargEndpoint other) { assert getDataType() == other.getDataType(); if (!isFinite() || !other.isFinite()) { return false; } if ((coordinate instanceof RexDynamicParam) || (other.coordinate instanceof RexDynamicParam)) { if ((coordinate instanceof RexDynamicParam) && (other.coordinate instanceof RexDynamicParam)) { // make sure it's the same param RexDynamicParam p1 = (RexDynamicParam) coordinate; RexDynamicParam p2 = (RexDynamicParam) other.coordinate; if (p1.getIndex() != p2.getIndex()) { return false; } } else { // one is a dynamic param but the other isn't return false; } } else if ( (coordinate instanceof RexInputRef) || (other.coordinate instanceof RexInputRef)) { if ((coordinate instanceof RexInputRef) && (other.coordinate instanceof RexInputRef)) { // make sure it's the same RexInputRef RexInputRef r1 = (RexInputRef) coordinate; RexInputRef r2 = (RexInputRef) other.coordinate; if (r1.getIndex() != r2.getIndex()) { return false; } } else { // one is a RexInputRef but the other isn't return false; } } else if (compareCoordinates(coordinate, other.coordinate) != 0) { return false; } return isClosed() || other.isClosed(); } static int compareCoordinates(RexNode coord1, RexNode coord2) { assert coord1 instanceof RexLiteral; assert coord2 instanceof RexLiteral; // null values always sort lowest boolean isNull1 = RexLiteral.isNullLiteral(coord1); boolean isNull2 = RexLiteral.isNullLiteral(coord2); if (isNull1 && isNull2) { return 0; } else if (isNull1) { return -1; } else if (isNull2) { return 1; } else { RexLiteral lit1 = (RexLiteral) coord1; RexLiteral lit2 = (RexLiteral) coord2; return lit1.getValue().compareTo(lit2.getValue()); } } // implement Object public String toString() { if (!isFinite()) { if (boundType == SargBoundType.LOWER) { return "-infinity"; } else { return "+infinity"; } } StringBuilder sb = new StringBuilder(); if (boundType == SargBoundType.LOWER) { if (isClosed()) { sb.append(">="); } else { sb.append(">"); } } else { if (isClosed()) { sb.append("<="); } else { sb.append("<"); } } sb.append(" "); sb.append(coordinate); return sb.toString(); } // implement Comparable public int compareTo(SargEndpoint other) { if (getInfinitude() != other.getInfinitude()) { // at least one is infinite; result is based on comparison of // infinitudes return getInfinitude() - other.getInfinitude(); } if (!isFinite()) { // both are the same infinity: equals return 0; } // both are finite: compare coordinates int c = compareCoordinates( getCoordinate(), other.getCoordinate()); if (c != 0) { return c; } // if coordinates are the same, then result is based on comparison of // strictness return getStrictnessSign() - other.getStrictnessSign(); } /** * @return SargStrictness of this bound */ public SargStrictness getStrictness() { return strictness; } /** * @return complement of SargStrictness of this bound */ public SargStrictness getStrictnessComplement() { return (strictness == SargStrictness.OPEN) ? SargStrictness.CLOSED : SargStrictness.OPEN; } /** * @return -1 for infinitesimally below (open upper bound, strictly less * than), 0 for exact equality (closed bound), 1 for infinitesimally above * (open lower bound, strictly greater than) */ public int getStrictnessSign() { if (strictness == SargStrictness.CLOSED) { return 0; } else { if (boundType == SargBoundType.LOWER) { return 1; } else { return -1; } } } // override Object public boolean equals(Object other) { if (!(other instanceof SargEndpoint)) { return false; } return compareTo((SargEndpoint) other) == 0; } // override Object public int hashCode() { return toString().hashCode(); } } // End SargEndpoint.java