/*
* 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.util.*;
import org.eigenbase.reltype.*;
import org.eigenbase.rex.*;
import org.eigenbase.util.*;
import com.google.common.collect.ImmutableList;
/**
* SargSetExpr represents the application of a {@link SargSetOperator set
* operator} to zero or more child {@link SargExpr sarg expressions}.
*/
public class SargSetExpr implements SargExpr {
//~ Instance fields --------------------------------------------------------
private final SargFactory factory;
private final RelDataType dataType;
private final SargSetOperator setOp;
private final List<SargExpr> children;
//~ Constructors -----------------------------------------------------------
/**
* @see SargFactory#newSetExpr
*/
SargSetExpr(
SargFactory factory,
RelDataType dataType,
SargSetOperator setOp) {
this.factory = factory;
this.dataType = dataType;
this.setOp = setOp;
children = new ArrayList<SargExpr>();
}
//~ Methods ----------------------------------------------------------------
/**
* @return a read-only list of this expression's children (the returned
* children themselves are modifiable)
*/
public List<SargExpr> getChildren() {
return ImmutableList.copyOf(children);
}
/**
* Adds a child to this expression.
*
* @param child child to add
*/
public void addChild(SargExpr child) {
assert child.getDataType() == dataType;
if (setOp == SargSetOperator.COMPLEMENT) {
assert children.isEmpty();
}
children.add(child);
}
// implement SargExpr
public SargFactory getFactory() {
return factory;
}
// implement SargExpr
public RelDataType getDataType() {
return dataType;
}
// implement SargExpr
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(setOp);
sb.append("(");
for (SargExpr child : children) {
sb.append(" ");
sb.append(child);
}
sb.append(" )");
return sb.toString();
}
// implement SargExpr
public SargIntervalSequence evaluate() {
if (setOp == SargSetOperator.COMPLEMENT) {
assert children.size() == 1;
SargExpr child = children.get(0);
return child.evaluateComplemented();
}
List<SargIntervalSequence> list = evaluateChildren(this);
switch (setOp) {
case UNION:
return evaluateUnion(list);
case INTERSECTION:
return evaluateIntersection(list);
default:
throw Util.newInternal(setOp.toString());
}
}
private List<SargIntervalSequence> evaluateChildren(SargSetExpr setExpr) {
List<SargIntervalSequence> list = new ArrayList<SargIntervalSequence>();
for (SargExpr child : setExpr.children) {
SargIntervalSequence newSeq = child.evaluate();
list.add(newSeq);
}
return list;
}
// implement SargExpr
public void collectDynamicParams(Set<RexDynamicParam> dynamicParams) {
for (SargExpr child : children) {
child.collectDynamicParams(dynamicParams);
}
}
private SargIntervalSequence evaluateUnion(List<SargIntervalSequence> list) {
SargIntervalSequence seq = new SargIntervalSequence();
// Toss all entries from each sequence in the list into one big sorted
// set.
SortedSet<SargInterval> intervals =
new TreeSet<SargInterval>(IntervalComparator.INSTANCE);
for (SargIntervalSequence childSeq : list) {
intervals.addAll(childSeq.getList());
}
// Now, overlapping ranges are consecutive in the set. Merge them by
// increasing the upper bound of the first; discard the others. In the
// example, [4, 6] and [5, 7) are combined to form [4, 7). (7, 8] is
// not merged with the new range because neither range contains the
// value 7.
//
// Input:
// 1 2 3 4 5 6 7 8 9
// 1 [1, 3] [-----]
// 2 [4, 6] [-----]
// 3 [5, 7) [-----)
// 4 (7, 8] (--]
//
// Output:
// 1 [1, 3] [-----]
// 2 [4, 7) [--------)
// 3 (7, 8] (--]
SargInterval accumulator = null;
for (SargInterval interval : intervals) {
// Empty intervals should have been previously filtered out.
assert !interval.isEmpty();
if (accumulator == null) {
// The very first interval: start accumulating.
accumulator =
new SargInterval(
factory,
getDataType());
accumulator.copyFrom(interval);
seq.addInterval(accumulator);
continue;
}
if (accumulator.contains(interval)) {
// Just drop new interval because it's already covered
// by accumulator.
continue;
}
// Test for overlap.
int c =
interval.getLowerBound().compareTo(
accumulator.getUpperBound());
// If no overlap, test for touching instead.
if (c > 0) {
if (interval.getLowerBound().isTouching(
accumulator.getUpperBound())) {
// Force test below to pass.
c = -1;
}
}
if (c <= 0) {
// Either touching or overlap: grow the accumulator.
accumulator.upperBound.copyFrom(interval.getUpperBound());
} else {
// Disjoint: start accumulating a new interval
accumulator =
new SargInterval(
factory,
getDataType());
accumulator.copyFrom(interval);
seq.addInterval(accumulator);
}
}
return seq;
}
private SargIntervalSequence evaluateIntersection(
List<SargIntervalSequence> list) {
SargIntervalSequence seq = null;
if (list.isEmpty()) {
// Counterintuitive but true: intersection of no sets is the
// universal set (kinda like 2^0=1). One way to prove this to
// yourself is to apply DeMorgan's law. The union of no sets is
// certainly the empty set. So the complement of that union is the
// universal set. That's equivalent to the intersection of the
// complements of no sets, which is the intersection of no sets.
// QED.
seq = new SargIntervalSequence();
seq.addInterval(
new SargInterval(
factory,
getDataType()));
return seq;
}
// The way we evaluate the intersection is to start with the first
// entry as a baseline, and then keep deleting stuff from it by
// intersecting the other entrie in turn. Whatever makes it through
// this filtering remains as the final result.
for (SargIntervalSequence newSeq : list) {
if (seq == null) {
// first child
seq = newSeq;
continue;
}
intersectSequences(seq, newSeq);
}
return seq;
}
private void intersectSequences(
SargIntervalSequence targetSeq,
SargIntervalSequence sourceSeq) {
ListIterator<SargInterval> targetIter = targetSeq.list.listIterator();
if (!targetIter.hasNext()) {
// No target intervals at all, so quit.
return;
}
ListIterator<SargInterval> sourceIter = sourceSeq.list.listIterator();
if (!sourceIter.hasNext()) {
// No source intervals at all, so result is empty
targetSeq.list.clear();
return;
}
// Start working on first source and target intervals
SargInterval target = targetIter.next();
SargInterval source = sourceIter.next();
// loop invariant: both source and target are non-null on entry
for (;;) {
if (source.getUpperBound().compareTo(target.getLowerBound()) < 0) {
// Source is completely below target; discard it and
// move on to next one.
if (!sourceIter.hasNext()) {
// No more sources.
break;
}
source = sourceIter.next();
continue;
}
if (target.getUpperBound().compareTo(source.getLowerBound()) < 0) {
// Target is completely below source; discard it and
// move on to next one.
targetIter.remove();
if (!targetIter.hasNext()) {
// All done.
return;
}
target = targetIter.next();
continue;
}
// Overlap case: perform intersection of the two intervals.
if (source.getLowerBound().compareTo(target.getLowerBound()) > 0) {
// Source starts after target starts, so trim the target.
target.setLower(
source.getLowerBound().getCoordinate(),
source.getLowerBound().getStrictness());
}
int c = source.getUpperBound().compareTo(target.getUpperBound());
if (c < 0) {
// The source ends before the target ends, so split the target
// into two parts. The first part will be kept for sure; the
// second part will be compared against further source ranges.
SargInterval newTarget =
new SargInterval(
factory,
dataType);
newTarget.setLower(
source.getUpperBound().getCoordinate(),
source.getUpperBound().getStrictnessComplement());
if (target.getUpperBound().isFinite()) {
newTarget.setUpper(
target.getUpperBound().getCoordinate(),
target.getUpperBound().getStrictness());
}
// Trim current target to exclude the part of the range
// which will move to newTarget.
target.setUpper(
source.getUpperBound().getCoordinate(),
source.getUpperBound().getStrictness());
// Insert newTarget after target. This makes newTarget
// into previous().
targetIter.add(newTarget);
// Next time through, work on newTarget.
// targetIter.previous() is pointing at the newTarget.
target = targetIter.previous();
// Now targetIter.next() is also pointing at the newTarget;
// need to do this redundant step to get targetIter in sync
// with target.
target = targetIter.next();
// Advance source.
if (!sourceIter.hasNext()) {
break;
}
source = sourceIter.next();
} else if (c == 0) {
// Source and target ends coincide, so advance both source and
// target.
if (!targetIter.hasNext()) {
return;
}
target = targetIter.next();
if (!sourceIter.hasNext()) {
break;
}
source = sourceIter.next();
} else {
// Source ends after target ends, so advance target.
assert c > 0;
if (!targetIter.hasNext()) {
return;
}
target = targetIter.next();
}
}
// Discard any remaining targets since they didn't have corresponding
// sources.
for (;;) {
targetIter.remove();
if (!targetIter.hasNext()) {
break;
}
targetIter.next();
}
}
// implement SargExpr
public SargIntervalSequence evaluateComplemented() {
if (setOp == SargSetOperator.COMPLEMENT) {
// Double negation is a nop
return children.get(0).evaluate();
}
// Use DeMorgan's Law: complement of union is intersection of
// complements, and vice versa
List<SargIntervalSequence> list = new ArrayList<SargIntervalSequence>();
for (SargExpr child : children) {
SargIntervalSequence newSeq = child.evaluateComplemented();
list.add(newSeq);
}
switch (setOp) {
case INTERSECTION:
return evaluateUnion(list);
case UNION:
return evaluateIntersection(list);
default:
throw Util.newInternal(setOp.toString());
}
}
//~ Inner Classes ----------------------------------------------------------
/**
* Comparator used in evaluateUnionOp. Intervals collate based on
* {lowerBound, upperBound}.
*/
private static class IntervalComparator implements Comparator<SargInterval> {
public static final IntervalComparator INSTANCE = new IntervalComparator();
private IntervalComparator() {
}
// implement Comparator
public int compare(SargInterval i1, SargInterval i2) {
int c = i1.getLowerBound().compareTo(i2.getLowerBound());
if (c != 0) {
return c;
}
return i1.getUpperBound().compareTo(i2.getUpperBound());
}
}
}
// End SargSetExpr.java