blob: 1ecf8ea76776224e221ed8912859bb6a113ff21e [file] [log] [blame]
/*
* 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.calcite.rex;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeField;
import org.apache.calcite.util.Litmus;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.List;
import static java.util.Objects.requireNonNull;
/**
* Visitor which checks the validity of a {@link RexNode} expression.
*
* <p>This visitor can operate in two modes, controlled by the {@link Litmus} argument:
* <ul>
* <li>{@link Litmus#THROW} causes the checker to throw an {@link AssertionError}
* as soon as an invalid node is detected. Note: This mode requires that assertions are enabled.
* <blockquote><pre>
* // Example usage of Litmus.THROW
* RexNode node;
* RelDataType rowType;
* RexChecker checker = new RexChecker(rowType, Litmus.THROW);
* node.accept(checker); // Will throw an AssertionError if node is invalid
* </pre></blockquote>
* </li>
* <li>{@link Litmus#IGNORE} tests validity without throwing an error.
* <blockquote><pre>
* // Example usage of Litmus.IGNORE
* RexNode node;
* RelDataType rowType;
* RexChecker checker = new RexChecker(rowType, Litmus.IGNORE);
* node.accept(checker);
* if (!checker.isValid()) {
* // Handle invalid node
* }
* </pre></blockquote>
* </li>
* </ul>
*
* @see RexNode
*/
public class RexChecker extends RexVisitorImpl<@Nullable Boolean> {
//~ Instance fields --------------------------------------------------------
protected final RelNode.@Nullable Context context;
protected final Litmus litmus;
protected final List<RelDataType> inputTypeList;
protected int failCount;
//~ Constructors -----------------------------------------------------------
/**
* Creates a RexChecker with a given input row type.
*
* <p>If <code>fail</code> is true, the checker will throw an
* {@link AssertionError} if an invalid node is found and assertions are
* enabled.
*
* <p>Otherwise, each method returns whether its part of the tree is valid.
*
* @param inputRowType Input row type
* @param context Context of the enclosing {@link RelNode}, or null
* @param litmus What to do if an invalid node is detected
*/
public RexChecker(final RelDataType inputRowType, RelNode.@Nullable Context context,
Litmus litmus) {
this(RelOptUtil.getFieldTypeList(inputRowType), context, litmus);
}
/**
* Creates a RexChecker with a given set of input fields.
*
* <p>If <code>fail</code> is true, the checker will throw an
* {@link AssertionError} if an invalid node is found and assertions are
* enabled.
*
* <p>Otherwise, each method returns whether its part of the tree is valid.
*
* @param inputTypeList Input row type
* @param context Context of the enclosing {@link RelNode}, or null
* @param litmus What to do if an error is detected
*/
public RexChecker(List<RelDataType> inputTypeList, RelNode.@Nullable Context context,
Litmus litmus) {
super(true);
this.inputTypeList = inputTypeList;
this.context = context;
this.litmus = litmus;
}
//~ Methods ----------------------------------------------------------------
/**
* Returns the number of failures encountered.
*
* @return Number of failures
*/
public int getFailureCount() {
return failCount;
}
@Override public Boolean visitInputRef(RexInputRef ref) {
final int index = ref.getIndex();
if ((index < 0) || (index >= inputTypeList.size())) {
++failCount;
return litmus.fail("RexInputRef index {} out of range 0..{}",
index, inputTypeList.size() - 1);
}
if (!ref.getType().isStruct()
&& !RelOptUtil.eq("ref", ref.getType(), "input",
inputTypeList.get(index), litmus)) {
++failCount;
return litmus.fail(null);
}
return litmus.succeed();
}
@Override public Boolean visitLocalRef(RexLocalRef ref) {
++failCount;
return litmus.fail("RexLocalRef illegal outside program");
}
@Override public Boolean visitCall(RexCall call) {
for (RexNode operand : call.getOperands()) {
Boolean valid = operand.accept(this);
if (valid != null && !valid) {
return litmus.fail(null);
}
}
return litmus.succeed();
}
@Override public Boolean visitFieldAccess(RexFieldAccess fieldAccess) {
super.visitFieldAccess(fieldAccess);
final RelDataType refType = fieldAccess.getReferenceExpr().getType();
assert refType.isStruct();
final RelDataTypeField field = fieldAccess.getField();
final int index = field.getIndex();
if ((index < 0) || (index >= refType.getFieldList().size())) {
++failCount;
return litmus.fail(null);
}
final RelDataTypeField typeField = refType.getFieldList().get(index);
if (!RelOptUtil.eq(
"type1",
typeField.getType(),
"type2",
fieldAccess.getType(), litmus)) {
++failCount;
return litmus.fail(null);
}
return litmus.succeed();
}
@Override public Boolean visitCorrelVariable(RexCorrelVariable v) {
if (context != null
&& !context.correlationIds().contains(v.id)) {
++failCount;
return litmus.fail("correlation id {} not found in correlation list {}",
v, context.correlationIds());
}
return litmus.succeed();
}
/**
* Returns whether an expression is valid.
*/
public final boolean isValid(RexNode expr) {
return requireNonNull(expr.accept(this),
() -> "expr.accept(RexChecker) for expr=" + expr);
}
}