/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.sql.optimizer.rule.range; import com.foundationdb.sql.optimizer.plan.ConstantExpression; import com.foundationdb.sql.optimizer.plan.ExpressionNode; public abstract class RangeEndpoint implements Comparable<RangeEndpoint> { public abstract boolean isUpperWild(); public abstract ConstantExpression getValueExpression(); public abstract Object getValue(); public abstract boolean isInclusive(); public abstract String describeValue(); @Override public int compareTo(RangeEndpoint o) { ComparisonResult comparison = compareEndpoints(this, o); switch (comparison) { case LT: case LT_BARELY: return -1; case GT: case GT_BARELY: return 1; case EQ: return 0; case INVALID: default: throw new IllegalComparisonException(this.getValue(), o.getValue()); } } public ComparisonResult comparePreciselyTo(RangeEndpoint other) { return compareEndpoints(this, other); } public static ValueEndpoint inclusive(ConstantExpression value) { return new ValueEndpoint(value, true); } public static ValueEndpoint exclusive(ConstantExpression value) { return new ValueEndpoint(value, false); } public static ValueEndpoint nullExclusive(ExpressionNode nodeOfMatchingType) { return exclusive(nullConstantExpression(nodeOfMatchingType)); } public static RangeEndpoint nullInclusive(ExpressionNode nodeOfMatchingType) { return inclusive(nullConstantExpression(nodeOfMatchingType)); } public static RangeEndpoint of(ConstantExpression value, boolean inclusive) { return new ValueEndpoint(value, inclusive); } private RangeEndpoint() {} /** * Returns whether the two endpoints are LT, GT or EQ to each other. * @param point1 the first point * @param point2 the second point * @return LT if point1 is less than point2; GT if point1 is greater than point2; EQ if point1 is greater than * point2; and INVALID if the two points can't be compared */ private static ComparisonResult compareEndpoints(RangeEndpoint point1, RangeEndpoint point2) { if (point1.equals(point2)) return ComparisonResult.EQ; // At this point we know they're not both upper wild. If either one is, we know the answer. if (point1.isUpperWild()) return ComparisonResult.GT; if (point2.isUpperWild()) return ComparisonResult.LT; // neither is wild ComparisonResult comparison = compareObjects(point1.getValue(), point2.getValue()); if (comparison == ComparisonResult.EQ && (point1.isInclusive() != point2.isInclusive())) { if (point1.isInclusive()) return ComparisonResult.LT_BARELY; assert point2.isInclusive() : point2; return ComparisonResult.GT_BARELY; } return comparison; } private static ConstantExpression nullConstantExpression(ExpressionNode nodeOfMatchingType) { return ConstantExpression.typedNull(nodeOfMatchingType.getSQLtype(), nodeOfMatchingType.getSQLsource(), nodeOfMatchingType.getType()); } @SuppressWarnings("unchecked") // We know that oneT and twoT are both Comparables of the same class. private static ComparisonResult compareObjects(Object one, Object two) { // if both are null, they're equal. Otherwise, at most one can be null; if either is null, we know the // answer. Otherwise, we know neither is null, and we can test their values (after checking the classes) if (one == two) return ComparisonResult.EQ; if (one == null) return ComparisonResult.LT; if (two == null) return ComparisonResult.GT; int compareResult; if (one.getClass().equals(two.getClass())) { if (!(one instanceof Comparable)) return ComparisonResult.INVALID; Comparable oneT = (Comparable) one; Comparable twoT = (Comparable) two; compareResult = (oneT).compareTo(twoT); } else if (((one.getClass() == Byte.class) || (one.getClass() == Short.class) || (one.getClass() == Integer.class) || (one.getClass() == Long.class)) && ((two.getClass() == Byte.class) || (two.getClass() == Short.class) || (two.getClass() == Integer.class) || (two.getClass() == Long.class))) { Number oneT = (Number) one; Number twoT = (Number) two; // TODO: JDK 7 this is in Long. compareResult = com.google.common.primitives.Longs.compare(oneT.longValue(), twoT.longValue()); } else return ComparisonResult.INVALID; if (compareResult < 0) return ComparisonResult.LT; else if (compareResult > 0) return ComparisonResult.GT; else return ComparisonResult.EQ; } public static final RangeEndpoint UPPER_WILD = new Wild(); private static class Wild extends RangeEndpoint { @Override public boolean isUpperWild() { return true; } @Override public Object getValue() { throw new UnsupportedOperationException(); } @Override public ConstantExpression getValueExpression() { return null; } @Override public boolean isInclusive() { return false; } @Override public String toString() { return "(*)"; } @Override public String describeValue() { return "*"; } } private static class ValueEndpoint extends RangeEndpoint { @Override public ConstantExpression getValueExpression() { return valueExpression; } @Override public Object getValue() { return valueExpression.getValue(); } @Override public boolean isInclusive() { return inclusive; } @Override public boolean isUpperWild() { return false; } @Override public String toString() { return valueExpression + (inclusive ? " inclusive" : " exclusive"); } @Override public String describeValue() { return valueExpression.toString(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ValueEndpoint that = (ValueEndpoint) o; return inclusive == that.inclusive && !(valueExpression != null ? !valueExpression.equals(that.valueExpression) : that.valueExpression != null); } @Override public int hashCode() { int result = valueExpression != null ? valueExpression.hashCode() : 0; result = 31 * result + (inclusive ? 1 : 0); return result; } private ValueEndpoint(ConstantExpression valueExpression, boolean inclusive) { this.valueExpression = valueExpression; this.inclusive = inclusive; } private ConstantExpression valueExpression; private boolean inclusive; } static class IllegalComparisonException extends RuntimeException { private IllegalComparisonException(Object one, Object two) { super(String.format("couldn't sort objects <%s> and <%s>", one, two )); } } enum RangePointComparison { MIN() { @Override protected Object select(Object one, Object two, ComparisonResult comparison) { return comparison == ComparisonResult.LT ? one : two; } }, MAX() { @Override protected Object select(Object one, Object two, ComparisonResult comparison) { return comparison == ComparisonResult.GT ? one : two; } } ; protected abstract Object select(Object one, Object two, ComparisonResult comparison); public Object get(Object one, Object two) { ComparisonResult comparisonResult = compareObjects(one, two); switch (comparisonResult) { case EQ: return one; case LT_BARELY: case LT: case GT_BARELY: case GT: return select(one, two, comparisonResult.normalize()); case INVALID: return null; default: throw new AssertionError(comparisonResult.name()); } } public static final Object INVALID_COMPARISON = new Object(); } }