/** * 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.server.types.texpressions.Comparison; import com.foundationdb.sql.optimizer.plan.ColumnExpression; import com.foundationdb.sql.optimizer.plan.ConstantExpression; import com.foundationdb.sql.optimizer.plan.ExpressionNode; import com.foundationdb.util.ArgumentValidation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.ListIterator; public final class RangeSegment { private static final Logger log = LoggerFactory.getLogger(RangeSegment.class); public static List<RangeSegment> fromComparison(Comparison op, ConstantExpression constantExpression) { final RangeEndpoint startPoint; final RangeEndpoint endPoint; switch (op) { case EQ: startPoint = endPoint = RangeEndpoint.inclusive(constantExpression); break; case LT: startPoint = RangeEndpoint.nullExclusive(constantExpression); endPoint = RangeEndpoint.exclusive(constantExpression); break; case LE: startPoint = RangeEndpoint.nullExclusive(constantExpression); endPoint = RangeEndpoint.inclusive(constantExpression); break; case GT: startPoint = RangeEndpoint.exclusive(constantExpression); endPoint = RangeEndpoint.UPPER_WILD; break; case GE: startPoint = RangeEndpoint.inclusive(constantExpression); endPoint = RangeEndpoint.UPPER_WILD; break; case NE: List<RangeSegment> result = new ArrayList<>(2); result.add(fromComparison(Comparison.LT, constantExpression).get(0)); result.add(fromComparison(Comparison.GT, constantExpression).get(0)); return result; default: throw new AssertionError(op.name()); } RangeSegment result = new RangeSegment(startPoint, endPoint); return Collections.singletonList(result); } static List<RangeSegment> sortAndCombine(List<RangeSegment> segments) { try { Collections.sort(segments, RANGE_SEGMENTS_BY_START); } catch (RangeEndpoint.IllegalComparisonException e) { log.warn("illegal comparison in sorting/combining range segments", e); return null; } // General algorithm: // - iterate over each RangeSegment. // - if this is the first RangeSegment, let it be. Otherwise... // -- if its start is <= the end of the previous one... // --- if its end is <= the end of the previous one, simply remove it // --- otherwise, remove both and replace them with a RangeSegment whose start is the previous one and whose end // is the new one; this is now the new previous // -- else, if start > the end of the previous one, this is the new current // // When comparing starts, WILD <= anything; and when comparing ends, WILD >= anything. RangeSegment previous = null; for (ListIterator<RangeSegment> iterator = segments.listIterator(); iterator.hasNext(); ) { RangeSegment currentSegment = iterator.next(); final RangeSegment nextPrevious; if (previous == null) { nextPrevious = currentSegment; } else { RangeEndpoint previousEnd = previous.getEnd(); RangeEndpoint currentStart = currentSegment.getStart(); // "start" and "end" are relative to the previous. So, startsOverlap specifies whether // the current's end is less than the previous start; and endsOverlap specifies whether the current's // end is less than the previous end Boolean startsOverlap = findOverlap(previousEnd, currentStart, true); if (startsOverlap == null) return null; if (startsOverlap) { Boolean endsOverlap = findOverlap(previousEnd, currentSegment.getEnd(), false); if (endsOverlap == null) return null; if (endsOverlap) { iterator.remove(); nextPrevious = previous; } // previous end is < current end; extend by taking previous start and current end else { nextPrevious = new RangeSegment(previous.getStart(), currentSegment.getEnd()); replacePreviousTwo(iterator, previous, nextPrevious); } } else { nextPrevious = currentSegment; } } previous = nextPrevious; } return segments; } private static void replacePreviousTwo(ListIterator<RangeSegment> iterator, RangeSegment previous, RangeSegment newValue) { // replace the previous iterator's value with this one iterator.set(newValue); // go back one; now looking at what we just set RangeSegment oneBack = iterator.previous(); assert oneBack == newValue : oneBack + " != " + newValue; // go back again; now looking at the previous iteration's RangeSegment RangeSegment twoBack = iterator.previous(); assert twoBack == previous : twoBack + " != " + previous; iterator.remove(); // go forward one; now back to looking at the one we just created RangeSegment nowAt = iterator.next(); assert nowAt == newValue : nowAt + " != " + newValue; } /** * Compares two RangePoints for overlap. The two overlap if high < low, or if the two are equal and at least * one of them is inclusive or wild. * * @param low the RangePoint which should be lower, if the two are not to overlap * @param high the RangePoint which should be higher, if the two are not to overlap * @param loose if true, a GT_BARELY comparison counts as an overlap; we want this when comparing ends, * but not starts * @return whether the two points overlap */ private static Boolean findOverlap(RangeEndpoint low, RangeEndpoint high, boolean loose) { ComparisonResult comparison = low.comparePreciselyTo(high); switch (comparison) { case GT_BARELY: return loose; // low > high only because of inclusiveness. Use the looseness. case EQ: return low.isInclusive(); // if they're (both) exclusive, it's a discontinuity case LT_BARELY: // fall through // low < high only because of inclusiveness. For starts, this is // an overlap, and for ends, it can't happen (due to sorting) case GT: return true; // low > high, this is always an overlap case LT: return false; // low < high, this is never an overlap case INVALID: return null; // the two weren't comparable default: throw new AssertionError(comparison.name()); } } static List<RangeSegment> orRanges(List<RangeSegment> leftRanges, List<RangeSegment> rightRanges) { List<RangeSegment> bothSegments = new ArrayList<>(leftRanges); bothSegments.addAll(rightRanges); return bothSegments; } static List<RangeSegment> andRanges(List<RangeSegment> leftRanges, List<RangeSegment> rightRanges) { List<RangeSegment> results = new ArrayList<>(); for (RangeSegment leftSegment : leftRanges) { for (RangeSegment rightSegment : rightRanges) { RangeSegment result = andRangeSegment(leftSegment, rightSegment); if (result != null) results.add(result); } } return results; } static RangeSegment andRangeSegment(RangeSegment left, RangeSegment right) { RangeEndpoint start = rangeEndpoint(left.getStart(), right.getStart(), RangeEndpoint.RangePointComparison.MAX); RangeEndpoint end = rangeEndpoint(left.getEnd(), right.getEnd(), RangeEndpoint.RangePointComparison.MIN); // if either null, a comparison failed and we should bail // otherwise, if start > end, this is an empty range and we should bail; another iteration of the loop // will give us the correct order // about the inclusivity factor in compareEndpoints: this only kicks in if both points have equal value // but different inclusivity. In this case, we want to reject the segment either way. So we'll nudge this // to GT if (start == null || end == null) return null; ComparisonResult comparison = start.comparePreciselyTo(end); switch (comparison) { case GT: case GT_BARELY: case LT_BARELY: return null; } return new RangeSegment(start, end); } static RangeEndpoint rangeEndpoint(RangeEndpoint one, RangeEndpoint two, RangeEndpoint.RangePointComparison comparison) { if (one.isUpperWild()) return comparison == RangeEndpoint.RangePointComparison.MAX ? one : two; if (two.isUpperWild()) return comparison == RangeEndpoint.RangePointComparison.MIN ? one : two; Object resultValue = comparison.get(one.getValue(), two.getValue()); if (resultValue == RangeEndpoint.RangePointComparison.INVALID_COMPARISON) return null; boolean resultInclusive = one.isInclusive() || two.isInclusive(); ConstantExpression resultExpression; if (resultValue == one.getValue()) resultExpression = one.getValueExpression(); else if (resultValue == two.getValue()) resultExpression = two.getValueExpression(); else throw new AssertionError(String.valueOf(resultValue)); return RangeEndpoint.of( resultExpression, resultInclusive ); } public RangeEndpoint getStart() { return start; } public RangeEndpoint getEnd() { return end; } public boolean isSingle() { return start.equals(end); } @Override public String toString() { StringBuilder sb = new StringBuilder(); if (isSingle()) { sb.append("% = "); sb.append(start.describeValue()); } else { sb.append(start.describeValue()); sb.append(start.isInclusive() ? " <= % " : " < % "); if (!end.isUpperWild()) { sb.append(end.isInclusive() ? "<= " : "< "); sb.append(end.describeValue()); } } return sb.toString(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; RangeSegment that = (RangeSegment) o; return end.equals(that.end) && start.equals(that.start); } @Override public int hashCode() { int result = start.hashCode(); result = 31 * result + end.hashCode(); return result; } public RangeSegment(RangeEndpoint start, RangeEndpoint end) { ArgumentValidation.notNull("start", start); ArgumentValidation.notNull("end", end); this.start = start; this.end = end; } private RangeEndpoint start; private RangeEndpoint end; private static final Comparator<? super RangeSegment> RANGE_SEGMENTS_BY_START = new Comparator<RangeSegment>() { @Override public int compare(RangeSegment segment1, RangeSegment segment2) { return segment1.getStart().compareTo(segment2.getStart()); } }; public static RangeSegment onlyNull(ExpressionNode expressionNode) { return new RangeSegment(RangeEndpoint.nullInclusive(expressionNode), RangeEndpoint.nullInclusive(expressionNode)); } }