/**
* 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.ComparisonCondition;
import com.foundationdb.sql.optimizer.plan.ConditionExpression;
import com.foundationdb.sql.optimizer.plan.ConstantExpression;
import com.foundationdb.sql.optimizer.plan.ExpressionNode;
import com.foundationdb.sql.optimizer.plan.FunctionCondition;
import com.foundationdb.sql.optimizer.plan.InListCondition;
import com.foundationdb.sql.optimizer.plan.LogicalFunctionCondition;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public final class ColumnRanges {
public static ColumnRanges rangeAtNode(ConditionExpression node) {
if (node instanceof ComparisonCondition) {
ComparisonCondition comparisonCondition = (ComparisonCondition) node;
return comparisonToRange(comparisonCondition);
}
else if (node instanceof LogicalFunctionCondition) {
LogicalFunctionCondition condition = (LogicalFunctionCondition) node;
if (condition.getOperands().size() != 2)
return null;
ColumnRanges leftRange = rangeAtNode(condition.getLeft());
ColumnRanges rightRange = rangeAtNode(condition.getRight());
if (leftRange != null && rightRange != null) {
List<RangeSegment> combinedSegments = combineBool(leftRange, rightRange, condition.getFunction());
if (combinedSegments != null) {
return new ColumnRanges(leftRange.getColumnExpression(), condition, combinedSegments);
}
}
}
else if (node instanceof FunctionCondition) {
FunctionCondition condition = (FunctionCondition) node;
if ("isNull".equals(condition.getFunction())) {
if (condition.getOperands().size() == 1) {
ExpressionNode operand = condition.getOperands().get(0);
if (operand instanceof ColumnExpression) {
ColumnExpression operandColumn = (ColumnExpression) operand;
return new ColumnRanges(operandColumn, condition,
Collections.singletonList(RangeSegment.onlyNull(operandColumn)));
}
}
}
}
else if (node instanceof InListCondition) {
InListCondition inListCondition = (InListCondition) node;
return inListToRange(inListCondition);
}
return null;
}
public static ColumnRanges andRanges(ColumnRanges left, ColumnRanges right) {
List<RangeSegment> combinedSegments = combineBool(left, right, true);
if (combinedSegments == null)
return null;
Set<ConditionExpression> combinedConditions = new HashSet<>(left.getConditions());
combinedConditions.addAll(right.getConditions());
return new ColumnRanges(left.getColumnExpression(), combinedConditions, combinedSegments);
}
private static List<RangeSegment> combineBool(ColumnRanges leftRange, ColumnRanges rightRange, boolean isAnd) {
if (!leftRange.getColumnExpression().equals(rightRange.getColumnExpression()))
return null;
List<RangeSegment> leftSegments = leftRange.getSegments();
List<RangeSegment> rightSegments = rightRange.getSegments();
List<RangeSegment> result;
if (isAnd)
result = RangeSegment.andRanges(leftSegments, rightSegments);
else
result = RangeSegment.orRanges(leftSegments, rightSegments);
if (result != null)
result = RangeSegment.sortAndCombine(result);
return result;
}
private static List<RangeSegment> combineBool(ColumnRanges leftRange, ColumnRanges rightRange, String logicOp) {
logicOp = logicOp.toLowerCase();
if ("and".endsWith(logicOp))
return combineBool(leftRange, rightRange, true);
else if ("or".equals(logicOp))
return combineBool(leftRange, rightRange, false);
else
return null;
}
public Collection<? extends ConditionExpression> getConditions() {
return rootConditions;
}
public List<RangeSegment> getSegments() {
return segments;
}
public ColumnExpression getColumnExpression() {
return columnExpression;
}
public String describeRanges() {
return segments.toString();
}
public boolean isAllSingle() {
boolean all = true;
for (RangeSegment segment : segments) {
if (!segment.isSingle()) {
return false;
}
}
return all;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ColumnRanges that = (ColumnRanges) o;
return columnExpression.equals(that.columnExpression)
&& rootConditions.equals(that.rootConditions)
&& segments.equals(that.segments);
}
@Override
public int hashCode() {
int result = columnExpression.hashCode();
result = 31 * result + rootConditions.hashCode();
result = 31 * result + segments.hashCode();
return result;
}
@Override
public String toString() {
return "Range " + columnExpression + ' ' + segments;
}
public ColumnRanges(ColumnExpression columnExpression, Set<? extends ConditionExpression> rootConditions,
List<RangeSegment> segments)
{
this.columnExpression = columnExpression;
this.rootConditions = rootConditions;
this.segments = segments;
}
public ColumnRanges(ColumnExpression columnExpression, ConditionExpression rootCondition,
List<RangeSegment> segments) {
this(columnExpression, Collections.singleton(rootCondition), segments);
}
private static ColumnRanges comparisonToRange(ComparisonCondition comparisonCondition) {
final ColumnExpression columnExpression;
final ExpressionNode other;
final boolean columnIsRight;
if (comparisonCondition.getLeft() instanceof ColumnExpression) {
columnExpression = (ColumnExpression) comparisonCondition.getLeft();
other = comparisonCondition.getRight();
columnIsRight = false;
}
else if (comparisonCondition.getRight() instanceof ColumnExpression) {
columnExpression = (ColumnExpression) comparisonCondition.getRight();
other = comparisonCondition.getLeft();
columnIsRight = true;
}
else {
return null;
}
if (other instanceof ConstantExpression) {
ConstantExpression constant = (ConstantExpression) other;
Comparison op = comparisonCondition.getOperation();
if (columnIsRight) {
op = flip(op);
}
List<RangeSegment> rangeSegments = RangeSegment.fromComparison(op, constant);
return new ColumnRanges(columnExpression, comparisonCondition, rangeSegments);
}
else {
return null;
}
}
private static Comparison flip(Comparison op) {
switch (op) {
case LT: return Comparison.GT;
case LE: return Comparison.GE;
case GT: return Comparison.LT;
case GE: return Comparison.LE;
case EQ:
case NE: return op;
default: throw new AssertionError(op.name());
}
}
private static ColumnRanges inListToRange(InListCondition inListCondition) {
final ColumnExpression columnExpression;
if (inListCondition.getOperand() instanceof ColumnExpression) {
columnExpression = (ColumnExpression)inListCondition.getOperand();
}
else {
return null;
}
List<ExpressionNode> expressions = inListCondition.getExpressions();
List<RangeSegment> rangeSegments = new ArrayList<>(expressions.size());
for (ExpressionNode expr : expressions) {
if (expr instanceof ConstantExpression) {
rangeSegments.addAll(RangeSegment.fromComparison(Comparison.EQ,
(ConstantExpression)expr));
}
else {
return null;
}
}
return new ColumnRanges(columnExpression, inListCondition, rangeSegments);
}
private ColumnExpression columnExpression;
private Set<? extends ConditionExpression> rootConditions;
private List<RangeSegment> segments;
}