org.apache.calcite.rex.RexInterpreter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of calcite-core Show documentation
Show all versions of calcite-core Show documentation
Core Calcite APIs and engine.
/*
* 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.avatica.util.DateTimeUtils;
import org.apache.calcite.avatica.util.TimeUnit;
import org.apache.calcite.avatica.util.TimeUnitRange;
import org.apache.calcite.rel.metadata.NullSentinel;
import org.apache.calcite.util.NlsString;
import org.apache.calcite.util.Util;
import com.google.common.collect.ImmutableMap;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.IntPredicate;
/**
* Evaluates {@link RexNode} expressions.
*
* Caveats:
*
* - It uses interpretation, so it is not very efficient.
*
- It is intended for testing, so does not cover very many functions and
* operators. (Feel free to contribute more!)
*
- It is not well tested.
*
*/
public class RexInterpreter implements RexVisitor {
private static final NullSentinel N = NullSentinel.INSTANCE;
private final Map environment;
/** Creates an interpreter.
*
* @param environment Values of certain expressions (usually
* {@link RexInputRef}s)
*/
private RexInterpreter(Map environment) {
this.environment = ImmutableMap.copyOf(environment);
}
/** Evaluates an expression in an environment. */
public static Comparable evaluate(RexNode e, Map map) {
final Comparable v = e.accept(new RexInterpreter(map));
if (false) {
System.out.println("evaluate " + e + " on " + map + " returns " + v);
}
return v;
}
private IllegalArgumentException unbound(RexNode e) {
return new IllegalArgumentException("unbound: " + e);
}
private Comparable getOrUnbound(RexNode e) {
final Comparable comparable = environment.get(e);
if (comparable != null) {
return comparable;
}
throw unbound(e);
}
public Comparable visitInputRef(RexInputRef inputRef) {
return getOrUnbound(inputRef);
}
public Comparable visitLocalRef(RexLocalRef localRef) {
throw unbound(localRef);
}
public Comparable visitLiteral(RexLiteral literal) {
return Util.first(literal.getValue4(), NullSentinel.INSTANCE);
}
public Comparable visitOver(RexOver over) {
throw unbound(over);
}
public Comparable visitCorrelVariable(RexCorrelVariable correlVariable) {
return getOrUnbound(correlVariable);
}
public Comparable visitDynamicParam(RexDynamicParam dynamicParam) {
return getOrUnbound(dynamicParam);
}
public Comparable visitRangeRef(RexRangeRef rangeRef) {
throw unbound(rangeRef);
}
public Comparable visitFieldAccess(RexFieldAccess fieldAccess) {
return getOrUnbound(fieldAccess);
}
public Comparable visitSubQuery(RexSubQuery subQuery) {
throw unbound(subQuery);
}
public Comparable visitTableInputRef(RexTableInputRef fieldRef) {
throw unbound(fieldRef);
}
public Comparable visitPatternFieldRef(RexPatternFieldRef fieldRef) {
throw unbound(fieldRef);
}
public Comparable visitCall(RexCall call) {
final List values = new ArrayList<>(call.operands.size());
for (RexNode operand : call.operands) {
values.add(operand.accept(this));
}
switch (call.getKind()) {
case EQUALS:
return compare(values, c -> c == 0);
case NOT_EQUALS:
return compare(values, c -> c != 0);
case GREATER_THAN:
return compare(values, c -> c > 0);
case GREATER_THAN_OR_EQUAL:
return compare(values, c -> c >= 0);
case LESS_THAN:
return compare(values, c -> c < 0);
case LESS_THAN_OR_EQUAL:
return compare(values, c -> c <= 0);
case AND:
return values.stream().map(Truthy::of).min(Comparator.naturalOrder())
.get().toComparable();
case OR:
return values.stream().map(Truthy::of).max(Comparator.naturalOrder())
.get().toComparable();
case NOT:
return not(values.get(0));
case CASE:
return case_(values);
case IS_TRUE:
return values.get(0).equals(true);
case IS_NOT_TRUE:
return !values.get(0).equals(true);
case IS_NULL:
return values.get(0).equals(N);
case IS_NOT_NULL:
return !values.get(0).equals(N);
case IS_FALSE:
return values.get(0).equals(false);
case IS_NOT_FALSE:
return !values.get(0).equals(false);
case PLUS:
return containsNull(values) ? N
: number(values.get(0)).add(number(values.get(1)));
case CAST:
return cast(call, values);
case COALESCE:
return coalesce(call, values);
case CEIL:
case FLOOR:
return ceil(call, values);
case EXTRACT:
return extract(call, values);
default:
throw unbound(call);
}
}
private Comparable extract(RexCall call, List values) {
final Comparable v = values.get(1);
if (v == N) {
return N;
}
final TimeUnitRange timeUnitRange = (TimeUnitRange) values.get(0);
final int v2;
if (v instanceof Long) {
// TIMESTAMP
v2 = (int) (((Long) v) / TimeUnit.DAY.multiplier.longValue());
} else {
// DATE
v2 = (Integer) v;
}
return DateTimeUtils.unixDateExtract(timeUnitRange, v2);
}
private Comparable coalesce(RexCall call, List values) {
for (Comparable value : values) {
if (value != N) {
return value;
}
}
return N;
}
private Comparable ceil(RexCall call, List values) {
if (values.get(0) == N) {
return N;
}
final Long v = (Long) values.get(0);
final TimeUnitRange unit = (TimeUnitRange) values.get(1);
switch (unit) {
case YEAR:
case MONTH:
switch (call.getKind()) {
case FLOOR:
return DateTimeUtils.unixTimestampFloor(unit, v);
default:
return DateTimeUtils.unixTimestampCeil(unit, v);
}
}
final TimeUnitRange subUnit = subUnit(unit);
for (long v2 = v;;) {
final int e = DateTimeUtils.unixTimestampExtract(subUnit, v2);
if (e == 0) {
return v2;
}
v2 -= unit.startUnit.multiplier.longValue();
}
}
private TimeUnitRange subUnit(TimeUnitRange unit) {
switch (unit) {
case QUARTER:
return TimeUnitRange.MONTH;
default:
return TimeUnitRange.DAY;
}
}
private Comparable cast(RexCall call, List values) {
if (values.get(0) == N) {
return N;
}
return values.get(0);
}
private Comparable not(Comparable value) {
if (value.equals(true)) {
return false;
} else if (value.equals(false)) {
return true;
} else {
return N;
}
}
private Comparable case_(List values) {
final int size;
final Comparable elseValue;
if (values.size() % 2 == 0) {
size = values.size();
elseValue = N;
} else {
size = values.size() - 1;
elseValue = Util.last(values);
}
for (int i = 0; i < size; i += 2) {
if (values.get(i).equals(true)) {
return values.get(i + 1);
}
}
return elseValue;
}
private BigDecimal number(Comparable comparable) {
return comparable instanceof BigDecimal
? (BigDecimal) comparable
: comparable instanceof BigInteger
? new BigDecimal((BigInteger) comparable)
: comparable instanceof Long
|| comparable instanceof Integer
|| comparable instanceof Short
? new BigDecimal(((Number) comparable).longValue())
: new BigDecimal(((Number) comparable).doubleValue());
}
private Comparable compare(List values, IntPredicate p) {
if (containsNull(values)) {
return N;
}
Comparable v0 = values.get(0);
Comparable v1 = values.get(1);
if (v0 instanceof Number && v1 instanceof NlsString) {
try {
v1 = new BigDecimal(((NlsString) v1).getValue());
} catch (NumberFormatException e) {
return false;
}
}
if (v1 instanceof Number && v0 instanceof NlsString) {
try {
v0 = new BigDecimal(((NlsString) v0).getValue());
} catch (NumberFormatException e) {
return false;
}
}
if (v0 instanceof Number) {
v0 = number(v0);
}
if (v1 instanceof Number) {
v1 = number(v1);
}
//noinspection unchecked
final int c = v0.compareTo(v1);
return p.test(c);
}
private boolean containsNull(List values) {
for (Comparable value : values) {
if (value == NullSentinel.INSTANCE) {
return true;
}
}
return false;
}
/** An enum that wraps boolean and unknown values and makes them
* comparable. */
enum Truthy {
// Order is important; AND returns the min, OR returns the max
FALSE, UNKNOWN, TRUE;
static Truthy of(Comparable c) {
return c.equals(true) ? TRUE : c.equals(false) ? FALSE : UNKNOWN;
}
Comparable toComparable() {
switch (this) {
case TRUE: return true;
case FALSE: return false;
case UNKNOWN: return N;
default:
throw new AssertionError();
}
}
}
}
// End RexInterpreter.java
© 2015 - 2024 Weber Informatics LLC | Privacy Policy