org.apache.iceberg.expressions.ExpressionVisitors Maven / Gradle / Ivy
Show all versions of iceberg-api Show documentation
/*
* 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.apache.iceberg.expressions;
import java.util.Set;
import org.apache.iceberg.exceptions.ValidationException;
/**
* Utils for traversing {@link Expression expressions}.
*/
public class ExpressionVisitors {
private ExpressionVisitors() {
}
public abstract static class ExpressionVisitor {
public R alwaysTrue() {
return null;
}
public R alwaysFalse() {
return null;
}
public R not(R result) {
return null;
}
public R and(R leftResult, R rightResult) {
return null;
}
public R or(R leftResult, R rightResult) {
return null;
}
public R predicate(BoundPredicate pred) {
return null;
}
public R predicate(UnboundPredicate pred) {
return null;
}
}
public abstract static class BoundExpressionVisitor extends ExpressionVisitor {
public R isNull(BoundReference ref) {
return null;
}
public R notNull(BoundReference ref) {
return null;
}
public R isNaN(BoundReference ref) {
throw new UnsupportedOperationException(this.getClass().getName() + " does not implement isNaN");
}
public R notNaN(BoundReference ref) {
throw new UnsupportedOperationException(this.getClass().getName() + " does not implement notNaN");
}
public R lt(BoundReference ref, Literal lit) {
return null;
}
public R ltEq(BoundReference ref, Literal lit) {
return null;
}
public R gt(BoundReference ref, Literal lit) {
return null;
}
public R gtEq(BoundReference ref, Literal lit) {
return null;
}
public R eq(BoundReference ref, Literal lit) {
return null;
}
public R notEq(BoundReference ref, Literal lit) {
return null;
}
public R in(BoundReference ref, Set literalSet) {
throw new UnsupportedOperationException("In expression is not supported by the visitor");
}
public R notIn(BoundReference ref, Set literalSet) {
throw new UnsupportedOperationException("notIn expression is not supported by the visitor");
}
public R startsWith(BoundReference ref, Literal lit) {
throw new UnsupportedOperationException("startsWith expression is not supported by the visitor");
}
public R notStartsWith(BoundReference ref, Literal lit) {
throw new UnsupportedOperationException("notStartsWith expression is not supported by the visitor");
}
/**
* Handle a non-reference value in this visitor.
*
* Visitors that require {@link BoundReference references} and not {@link Bound terms} can use this method to
* return a default value for expressions with non-references. The default implementation will throw a validation
* exception because the non-reference is not supported.
*
* @param term a non-reference bound expression
* @param a Java return type
* @return a return value for the visitor
*/
public R handleNonReference(Bound term) {
throw new ValidationException("Visitor %s does not support non-reference: %s", this, term);
}
@Override
public R predicate(BoundPredicate pred) {
if (!(pred.term() instanceof BoundReference)) {
return handleNonReference(pred.term());
}
if (pred.isLiteralPredicate()) {
BoundLiteralPredicate literalPred = pred.asLiteralPredicate();
switch (pred.op()) {
case LT:
return lt((BoundReference) pred.term(), literalPred.literal());
case LT_EQ:
return ltEq((BoundReference) pred.term(), literalPred.literal());
case GT:
return gt((BoundReference) pred.term(), literalPred.literal());
case GT_EQ:
return gtEq((BoundReference) pred.term(), literalPred.literal());
case EQ:
return eq((BoundReference) pred.term(), literalPred.literal());
case NOT_EQ:
return notEq((BoundReference) pred.term(), literalPred.literal());
case STARTS_WITH:
return startsWith((BoundReference) pred.term(), literalPred.literal());
case NOT_STARTS_WITH:
return notStartsWith((BoundReference) pred.term(), literalPred.literal());
default:
throw new IllegalStateException("Invalid operation for BoundLiteralPredicate: " + pred.op());
}
} else if (pred.isUnaryPredicate()) {
switch (pred.op()) {
case IS_NULL:
return isNull((BoundReference) pred.term());
case NOT_NULL:
return notNull((BoundReference) pred.term());
case IS_NAN:
return isNaN((BoundReference) pred.term());
case NOT_NAN:
return notNaN((BoundReference) pred.term());
default:
throw new IllegalStateException("Invalid operation for BoundUnaryPredicate: " + pred.op());
}
} else if (pred.isSetPredicate()) {
switch (pred.op()) {
case IN:
return in((BoundReference) pred.term(), pred.asSetPredicate().literalSet());
case NOT_IN:
return notIn((BoundReference) pred.term(), pred.asSetPredicate().literalSet());
default:
throw new IllegalStateException("Invalid operation for BoundSetPredicate: " + pred.op());
}
}
throw new IllegalStateException("Unsupported bound predicate: " + pred.getClass().getName());
}
@Override
public R predicate(UnboundPredicate pred) {
throw new UnsupportedOperationException("Not a bound predicate: " + pred);
}
}
public abstract static class BoundVisitor extends ExpressionVisitor {
public R isNull(Bound expr) {
return null;
}
public R notNull(Bound expr) {
return null;
}
public R isNaN(Bound expr) {
throw new UnsupportedOperationException(this.getClass().getName() + " does not implement isNaN");
}
public R notNaN(Bound expr) {
throw new UnsupportedOperationException(this.getClass().getName() + " does not implement notNaN");
}
public R lt(Bound expr, Literal lit) {
return null;
}
public R ltEq(Bound expr, Literal lit) {
return null;
}
public R gt(Bound expr, Literal lit) {
return null;
}
public R gtEq(Bound expr, Literal lit) {
return null;
}
public R eq(Bound expr, Literal lit) {
return null;
}
public R notEq(Bound expr, Literal lit) {
return null;
}
public R in(Bound expr, Set literalSet) {
throw new UnsupportedOperationException("In operation is not supported by the visitor");
}
public R notIn(Bound expr, Set literalSet) {
throw new UnsupportedOperationException("notIn operation is not supported by the visitor");
}
public R startsWith(Bound expr, Literal lit) {
throw new UnsupportedOperationException("Unsupported operation.");
}
public R notStartsWith(Bound expr, Literal lit) {
throw new UnsupportedOperationException("Unsupported operation.");
}
@Override
public R predicate(BoundPredicate pred) {
if (pred.isLiteralPredicate()) {
BoundLiteralPredicate literalPred = pred.asLiteralPredicate();
switch (pred.op()) {
case LT:
return lt(pred.term(), literalPred.literal());
case LT_EQ:
return ltEq(pred.term(), literalPred.literal());
case GT:
return gt(pred.term(), literalPred.literal());
case GT_EQ:
return gtEq(pred.term(), literalPred.literal());
case EQ:
return eq(pred.term(), literalPred.literal());
case NOT_EQ:
return notEq(pred.term(), literalPred.literal());
case STARTS_WITH:
return startsWith(pred.term(), literalPred.literal());
case NOT_STARTS_WITH:
return notStartsWith(pred.term(), literalPred.literal());
default:
throw new IllegalStateException("Invalid operation for BoundLiteralPredicate: " + pred.op());
}
} else if (pred.isUnaryPredicate()) {
switch (pred.op()) {
case IS_NULL:
return isNull(pred.term());
case NOT_NULL:
return notNull(pred.term());
case IS_NAN:
return isNaN(pred.term());
case NOT_NAN:
return notNaN(pred.term());
default:
throw new IllegalStateException("Invalid operation for BoundUnaryPredicate: " + pred.op());
}
} else if (pred.isSetPredicate()) {
switch (pred.op()) {
case IN:
return in(pred.term(), pred.asSetPredicate().literalSet());
case NOT_IN:
return notIn(pred.term(), pred.asSetPredicate().literalSet());
default:
throw new IllegalStateException("Invalid operation for BoundSetPredicate: " + pred.op());
}
}
throw new IllegalStateException("Unsupported bound predicate: " + pred.getClass().getName());
}
@Override
public R predicate(UnboundPredicate pred) {
throw new UnsupportedOperationException("Not a bound predicate: " + pred);
}
}
/**
* Traverses the given {@link Expression expression} with a {@link ExpressionVisitor visitor}.
*
* The visitor will be called to handle each node in the expression tree in postfix order. Result
* values produced by child nodes are passed when parent nodes are handled.
*
* @param expr an expression to traverse
* @param visitor a visitor that will be called to handle each node in the expression tree
* @param the return type produced by the expression visitor
* @return the value returned by the visitor for the root expression node
*/
public static R visit(Expression expr, ExpressionVisitor visitor) {
if (expr instanceof Predicate) {
if (expr instanceof BoundPredicate) {
return visitor.predicate((BoundPredicate>) expr);
} else {
return visitor.predicate((UnboundPredicate>) expr);
}
} else {
switch (expr.op()) {
case TRUE:
return visitor.alwaysTrue();
case FALSE:
return visitor.alwaysFalse();
case NOT:
Not not = (Not) expr;
return visitor.not(visit(not.child(), visitor));
case AND:
And and = (And) expr;
return visitor.and(visit(and.left(), visitor), visit(and.right(), visitor));
case OR:
Or or = (Or) expr;
return visitor.or(visit(or.left(), visitor), visit(or.right(), visitor));
default:
throw new UnsupportedOperationException(
"Unknown operation: " + expr.op());
}
}
}
/**
* Traverses the given {@link Expression expression} with a {@link ExpressionVisitor visitor}.
*
* The visitor will be called to handle only nodes required for determining result
* in the expression tree in postfix order. Result values produced by child nodes
* are passed when parent nodes are handled.
*
* @param expr an expression to traverse
* @param visitor a visitor that will be called to handle each node in the expression tree
* @return the value returned by the visitor for the root expression node
*/
public static Boolean visitEvaluator(Expression expr, ExpressionVisitor visitor) {
if (expr instanceof Predicate) {
if (expr instanceof BoundPredicate) {
return visitor.predicate((BoundPredicate>) expr);
} else {
return visitor.predicate((UnboundPredicate>) expr);
}
} else {
switch (expr.op()) {
case TRUE:
return visitor.alwaysTrue();
case FALSE:
return visitor.alwaysFalse();
case NOT:
Not not = (Not) expr;
return visitor.not(visitEvaluator(not.child(), visitor));
case AND:
And and = (And) expr;
Boolean andLeftOperand = visitEvaluator(and.left(), visitor);
if (!andLeftOperand) {
return visitor.alwaysFalse();
}
return visitor.and(Boolean.TRUE, visitEvaluator(and.right(), visitor));
case OR:
Or or = (Or) expr;
Boolean orLeftOperand = visitEvaluator(or.left(), visitor);
if (orLeftOperand) {
return visitor.alwaysTrue();
}
return visitor.or(Boolean.FALSE, visitEvaluator(or.right(), visitor));
default:
throw new UnsupportedOperationException(
"Unknown operation: " + expr.op());
}
}
}
}