/*
* Licensed 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 com.facebook.presto.sql.planner;
import com.facebook.presto.metadata.FunctionInfo;
import com.facebook.presto.metadata.Metadata;
import com.facebook.presto.operator.scalar.UnixTimeFunctions;
import com.facebook.presto.spi.RecordCursor;
import com.facebook.presto.sql.Casts;
import com.facebook.presto.sql.analyzer.Session;
import com.facebook.presto.sql.analyzer.Type;
import com.facebook.presto.sql.tree.ArithmeticExpression;
import com.facebook.presto.sql.tree.AstVisitor;
import com.facebook.presto.sql.tree.BetweenPredicate;
import com.facebook.presto.sql.tree.BooleanLiteral;
import com.facebook.presto.sql.tree.Cast;
import com.facebook.presto.sql.tree.CoalesceExpression;
import com.facebook.presto.sql.tree.ComparisonExpression;
import com.facebook.presto.sql.tree.CurrentTime;
import com.facebook.presto.sql.tree.Expression;
import com.facebook.presto.sql.tree.Extract;
import com.facebook.presto.sql.tree.FunctionCall;
import com.facebook.presto.sql.tree.IfExpression;
import com.facebook.presto.sql.tree.InListExpression;
import com.facebook.presto.sql.tree.InPredicate;
import com.facebook.presto.sql.tree.Input;
import com.facebook.presto.sql.tree.InputReference;
import com.facebook.presto.sql.tree.IsNotNullPredicate;
import com.facebook.presto.sql.tree.IsNullPredicate;
import com.facebook.presto.sql.tree.LikePredicate;
import com.facebook.presto.sql.tree.Literal;
import com.facebook.presto.sql.tree.LogicalBinaryExpression;
import com.facebook.presto.sql.tree.NegativeExpression;
import com.facebook.presto.sql.tree.Node;
import com.facebook.presto.sql.tree.NotExpression;
import com.facebook.presto.sql.tree.NullIfExpression;
import com.facebook.presto.sql.tree.NullLiteral;
import com.facebook.presto.sql.tree.QualifiedNameReference;
import com.facebook.presto.sql.tree.SearchedCaseExpression;
import com.facebook.presto.sql.tree.SimpleCaseExpression;
import com.facebook.presto.sql.tree.StringLiteral;
import com.facebook.presto.sql.tree.WhenClause;
import com.facebook.presto.tuple.TupleReadable;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Throwables;
import com.google.common.collect.Iterables;
import io.airlift.slice.Slice;
import io.airlift.slice.Slices;
import org.joni.Regex;
import javax.annotation.Nullable;
import java.lang.invoke.MethodHandle;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Set;
import static com.facebook.presto.sql.planner.LiteralInterpreter.toExpression;
import static com.facebook.presto.sql.planner.LiteralInterpreter.toExpressions;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
public class ExpressionInterpreter
{
private final Expression expression;
private final Metadata metadata;
private final Session session;
private final boolean optimize;
private final Visitor visitor;
// identity-based cache for LIKE expressions with constant pattern and escape char
private final IdentityHashMap<LikePredicate, Regex> likePatternCache = new IdentityHashMap<>();
private final IdentityHashMap<InListExpression, Set<Object>> inListCache = new IdentityHashMap<>();
public static ExpressionInterpreter expressionInterpreter(Expression expression, Metadata metadata, Session session)
{
checkNotNull(expression, "expression is null");
checkNotNull(metadata, "metadata is null");
checkNotNull(session, "session is null");
return new ExpressionInterpreter(expression, metadata, session, false);
}
public static ExpressionInterpreter expressionOptimizer(Expression expression, Metadata metadata, Session session)
{
checkNotNull(expression, "expression is null");
checkNotNull(metadata, "metadata is null");
checkNotNull(session, "session is null");
return new ExpressionInterpreter(expression, metadata, session, true);
}
private ExpressionInterpreter(Expression expression, Metadata metadata, Session session, boolean optimize)
{
this.expression = expression;
this.metadata = metadata;
this.session = session;
this.optimize = optimize;
this.visitor = new Visitor();
}
public Object evaluate(RecordCursor inputs)
{
Preconditions.checkState(!optimize, "evaluate(RecordCursor) not allowed for optimizer");
return visitor.process(expression, inputs);
}
public Object evaluate(TupleReadable[] inputs)
{
Preconditions.checkState(!optimize, "evaluate(TupleReadable[]) not allowed for optimizer");
return visitor.process(expression, inputs);
}
public Object optimize(SymbolResolver inputs)
{
Preconditions.checkState(optimize, "evaluate(SymbolResolver) not allowed for interpreter");
return visitor.process(expression, inputs);
}
private class Visitor
extends AstVisitor<Object, Object>
{
@Override
protected Object visitCurrentTime(CurrentTime node, Object context)
{
if (node.getType() != CurrentTime.Type.TIMESTAMP) {
throw new UnsupportedOperationException("not yet implemented: " + node.getType());
}
else if (node.getPrecision() != null) {
throw new UnsupportedOperationException("not yet implemented: non-default precision");
}
return UnixTimeFunctions.currentTimestamp(session);
}
@Override
public Object visitInputReference(InputReference node, Object context)
{
Input input = node.getInput();
int channel = input.getChannel();
if (context instanceof TupleReadable[]) {
TupleReadable[] inputs = (TupleReadable[]) context;
TupleReadable tuple = inputs[channel];
if (tuple.isNull()) {
return null;
}
switch (tuple.getTupleInfo().getType()) {
case BOOLEAN:
return tuple.getBoolean();
case FIXED_INT_64:
return tuple.getLong();
case DOUBLE:
return tuple.getDouble();
case VARIABLE_BINARY:
return tuple.getSlice();
default:
throw new UnsupportedOperationException("not yet implemented");
}
}
else if (context instanceof RecordCursor) {
RecordCursor cursor = (RecordCursor) context;
if (cursor.isNull(channel)) {
return null;
}
switch (cursor.getType(input.getChannel())) {
case BOOLEAN:
return cursor.getBoolean(channel);
case LONG:
return cursor.getLong(channel);
case DOUBLE:
return cursor.getDouble(channel);
case STRING:
return Slices.wrappedBuffer(cursor.getString(channel));
default:
throw new UnsupportedOperationException("not yet implemented");
}
}
throw new UnsupportedOperationException("Inputs or cursor myst be set");
}
@Override
protected Object visitQualifiedNameReference(QualifiedNameReference node, Object context)
{
if (node.getName().getPrefix().isPresent()) {
// not a symbol
return node;
}
Symbol symbol = Symbol.fromQualifiedName(node.getName());
return ((SymbolResolver) context).getValue(symbol);
}
@Override
protected Object visitLiteral(Literal node, Object context)
{
return LiteralInterpreter.evaluate(node);
}
@Override
protected Object visitIsNullPredicate(IsNullPredicate node, Object context)
{
Object value = process(node.getValue(), context);
if (value instanceof Expression) {
return new IsNullPredicate(toExpression(value));
}
return value == null;
}
@Override
protected Object visitIsNotNullPredicate(IsNotNullPredicate node, Object context)
{
Object value = process(node.getValue(), context);
if (value instanceof Expression) {
return new IsNotNullPredicate(toExpression(value));
}
return value != null;
}
@Override
protected Object visitSearchedCaseExpression(SearchedCaseExpression node, Object context)
{
Expression resultClause = node.getDefaultValue();
for (WhenClause whenClause : node.getWhenClauses()) {
Object value = process(whenClause.getOperand(), context);
if (value instanceof Expression) {
// TODO: optimize this case
return node;
}
if (Boolean.TRUE.equals(value)) {
resultClause = whenClause.getResult();
break;
}
}
if (resultClause == null) {
return null;
}
Object result = process(resultClause, context);
if (result instanceof Expression) {
return node;
}
return result;
}
@Override
protected Object visitSimpleCaseExpression(SimpleCaseExpression node, Object context)
{
Object operand = process(node.getOperand(), context);
if (operand instanceof Expression) {
// TODO: optimize this case
return node;
}
Expression resultClause = node.getDefaultValue();
if (operand != null) {
for (WhenClause whenClause : node.getWhenClauses()) {
Object value = process(whenClause.getOperand(), context);
if (value instanceof Expression) {
// TODO: optimize this case
return node;
}
if (operand instanceof Long && value instanceof Long) {
if (((Long) operand).longValue() == ((Long) value).longValue()) {
resultClause = whenClause.getResult();
break;
}
}
else if (operand instanceof Number && value instanceof Number) {
if (((Number) operand).doubleValue() == ((Number) value).doubleValue()) {
resultClause = whenClause.getResult();
break;
}
}
else if (operand.equals(value)) {
resultClause = whenClause.getResult();
break;
}
}
}
if (resultClause == null) {
return null;
}
Object result = process(resultClause, context);
if (result instanceof Expression) {
return node;
}
return result;
}
@Override
protected Object visitCoalesceExpression(CoalesceExpression node, Object context)
{
for (Expression expression : node.getOperands()) {
Object value = process(expression, context);
if (value instanceof Expression) {
// TODO: optimize this case
return node;
}
if (value != null) {
return value;
}
}
return null;
}
@Override
protected Object visitInPredicate(InPredicate node, Object context)
{
Object value = process(node.getValue(), context);
if (value == null) {
return null;
}
Expression valueListExpression = node.getValueList();
if (!(valueListExpression instanceof InListExpression)) {
if (!optimize) {
throw new UnsupportedOperationException("IN predicate value list type not yet implemented: " + valueListExpression.getClass().getName());
}
return node;
}
InListExpression valueList = (InListExpression) valueListExpression;
Set<Object> set = inListCache.get(valueList);
// We use the presence of the node in the map to indicate that we've already done
// the analysis below. If the value is null, it means that we can't apply the HashSet
// optimization
if (!inListCache.containsKey(valueList)) {
if (Iterables.all(valueList.getValues(), isNonNullLiteralPredicate())) {
// if all elements are constant, create a set with them
set = new HashSet<>();
for (Expression expression : valueList.getValues()) {
set.add(process(expression, context));
}
}
inListCache.put(valueList, set);
}
if (set != null && !(value instanceof Expression)) {
return set.contains(value);
}
boolean hasUnresolvedValue = false;
if (value instanceof Expression) {
hasUnresolvedValue = true;
}
boolean hasNullValue = false;
boolean found = false;
List<Object> values = new ArrayList<>(valueList.getValues().size());
for (Expression expression : valueList.getValues()) {
Object inValue = process(expression, context);
if (value instanceof Expression || inValue instanceof Expression) {
hasUnresolvedValue = true;
values.add(inValue);
continue;
}
if (inValue == null) {
hasNullValue = true;
}
else if (!found && value.equals(inValue)) {
// in does not short-circuit so we must evaluate all value in the list
found = true;
}
}
if (found) {
return true;
}
if (hasUnresolvedValue) {
return new InPredicate(toExpression(value), new InListExpression(toExpressions(values)));
}
if (hasNullValue) {
return null;
}
return false;
}
@Override
protected Object visitNegativeExpression(NegativeExpression node, Object context)
{
Object value = process(node.getValue(), context);
if (value == null) {
return null;
}
if (value instanceof Expression) {
return new NegativeExpression(toExpression(value));
}
if (value instanceof Long) {
return -((long) value);
}
return -((double) value);
}
@Override
protected Object visitArithmeticExpression(ArithmeticExpression node, Object context)
{
Object left = process(node.getLeft(), context);
if (left == null) {
return null;
}
Object right = process(node.getRight(), context);
if (right == null) {
return null;
}
if (left instanceof Expression || right instanceof Expression) {
return new ArithmeticExpression(node.getType(), toExpression(left), toExpression(right));
}
Number leftNumber = (Number) left;
Number rightNumber = (Number) right;
switch (node.getType()) {
case ADD:
if (leftNumber instanceof Long && rightNumber instanceof Long) {
return leftNumber.longValue() + rightNumber.longValue();
}
else {
return leftNumber.doubleValue() + rightNumber.doubleValue();
}
case SUBTRACT:
if (leftNumber instanceof Long && rightNumber instanceof Long) {
return leftNumber.longValue() - rightNumber.longValue();
}
else {
return leftNumber.doubleValue() - rightNumber.doubleValue();
}
case DIVIDE:
if (leftNumber instanceof Long && rightNumber instanceof Long) {
return leftNumber.longValue() / rightNumber.longValue();
}
else {
return leftNumber.doubleValue() / rightNumber.doubleValue();
}
case MULTIPLY:
if (leftNumber instanceof Long && rightNumber instanceof Long) {
return leftNumber.longValue() * rightNumber.longValue();
}
else {
return leftNumber.doubleValue() * rightNumber.doubleValue();
}
case MODULUS:
if (leftNumber instanceof Long && rightNumber instanceof Long) {
return leftNumber.longValue() % rightNumber.longValue();
}
else {
return leftNumber.doubleValue() % rightNumber.doubleValue();
}
default:
throw new UnsupportedOperationException("not yet implemented: " + node.getType());
}
}
@Override
protected Object visitComparisonExpression(ComparisonExpression node, Object context)
{
if (node.getType() == ComparisonExpression.Type.IS_DISTINCT_FROM) {
Object left = process(node.getLeft(), context);
Object right = process(node.getRight(), context);
if (left == null && right == null) {
return false;
}
else if (left == null || right == null) {
return true;
}
else if (left instanceof Long && right instanceof Long) {
return ((Number) left).longValue() != ((Number) right).longValue();
}
else if (left instanceof Number && right instanceof Number) {
return ((Number) left).doubleValue() != ((Number) right).doubleValue();
}
else if (left instanceof Boolean && right instanceof Boolean) {
return !left.equals(right);
}
else if (left instanceof Slice && right instanceof Slice) {
return !left.equals(right);
}
return new ComparisonExpression(node.getType(), toExpression(left), toExpression(right));
}
Object left = process(node.getLeft(), context);
if (left == null) {
return null;
}
Object right = process(node.getRight(), context);
if (right == null) {
return null;
}
if (left instanceof Long && right instanceof Long) {
switch (node.getType()) {
case EQUAL:
return ((Number) left).longValue() == ((Number) right).longValue();
case NOT_EQUAL:
return ((Number) left).longValue() != ((Number) right).longValue();
case LESS_THAN:
return ((Number) left).longValue() < ((Number) right).longValue();
case LESS_THAN_OR_EQUAL:
return ((Number) left).longValue() <= ((Number) right).longValue();
case GREATER_THAN:
return ((Number) left).longValue() > ((Number) right).longValue();
case GREATER_THAN_OR_EQUAL:
return ((Number) left).longValue() >= ((Number) right).longValue();
}
throw new UnsupportedOperationException("unhandled type: " + node.getType());
}
if (left instanceof Number && right instanceof Number) {
switch (node.getType()) {
case EQUAL:
return ((Number) left).doubleValue() == ((Number) right).doubleValue();
case NOT_EQUAL:
return ((Number) left).doubleValue() != ((Number) right).doubleValue();
case LESS_THAN:
return ((Number) left).doubleValue() < ((Number) right).doubleValue();
case LESS_THAN_OR_EQUAL:
return ((Number) left).doubleValue() <= ((Number) right).doubleValue();
case GREATER_THAN:
return ((Number) left).doubleValue() > ((Number) right).doubleValue();
case GREATER_THAN_OR_EQUAL:
return ((Number) left).doubleValue() >= ((Number) right).doubleValue();
}
throw new UnsupportedOperationException("unhandled type: " + node.getType());
}
if (left instanceof Slice && right instanceof Slice) {
switch (node.getType()) {
case EQUAL:
return left.equals(right);
case NOT_EQUAL:
return !left.equals(right);
case LESS_THAN:
return ((Slice) left).compareTo((Slice) right) < 0;
case LESS_THAN_OR_EQUAL:
return ((Slice) left).compareTo((Slice) right) <= 0;
case GREATER_THAN:
return ((Slice) left).compareTo((Slice) right) > 0;
case GREATER_THAN_OR_EQUAL:
return ((Slice) left).compareTo((Slice) right) >= 0;
}
throw new UnsupportedOperationException("unhandled type: " + node.getType());
}
if (left instanceof Boolean && right instanceof Boolean) {
switch (node.getType()) {
case EQUAL:
return left.equals(right);
case NOT_EQUAL:
return !left.equals(right);
}
throw new UnsupportedOperationException("unhandled type: " + node.getType());
}
return new ComparisonExpression(node.getType(), toExpression(left), toExpression(right));
}
@Override
protected Object visitBetweenPredicate(BetweenPredicate node, Object context)
{
Object value = process(node.getValue(), context);
if (value == null) {
return null;
}
Object min = process(node.getMin(), context);
if (min == null) {
return null;
}
Object max = process(node.getMax(), context);
if (max == null) {
return null;
}
if (value instanceof Number && min instanceof Number && max instanceof Number) {
return ((Number) min).doubleValue() <= ((Number) value).doubleValue() && ((Number) value).doubleValue() <= ((Number) max).doubleValue();
}
else if (value instanceof Slice && min instanceof Slice && max instanceof Slice) {
return ((Slice) min).compareTo((Slice) value) <= 0 && ((Slice) value).compareTo((Slice) max) <= 0;
}
return new BetweenPredicate(toExpression(value), toExpression(min), toExpression(max));
}
@Override
protected Object visitNullIfExpression(NullIfExpression node, Object context)
{
Object first = process(node.getFirst(), context);
if (first == null) {
return null;
}
Object second = process(node.getSecond(), context);
if (second == null) {
return first;
}
if (first instanceof Long && second instanceof Long) {
return ((Long) first).longValue() == ((Long) second).longValue() ? null : first;
}
else if (first instanceof Number && second instanceof Number) {
return ((Number) first).doubleValue() == ((Number) second).doubleValue() ? null : first;
}
else if (first instanceof Boolean && second instanceof Boolean) {
return first.equals(second) ? null : first;
}
else if (first instanceof Slice && second instanceof Slice) {
return first.equals(second) ? null : first;
}
return new NullIfExpression(toExpression(first), toExpression(second));
}
@Override
protected Object visitIfExpression(IfExpression node, Object context)
{
Object condition = process(node.getCondition(), context);
if (Boolean.TRUE.equals(condition)) {
return process(node.getTrueValue(), context);
}
if ((condition == null) || (Boolean.FALSE.equals(condition))) {
if (node.getFalseValue().isPresent()) {
return process(node.getFalseValue().get(), context);
}
return null;
}
Object trueValue = optimize(node.getTrueValue(), context);
Object falseValue = null;
if (node.getFalseValue().isPresent()) {
falseValue = optimize(node.getFalseValue().get(), context);
}
return new IfExpression(toExpression(condition), toExpression(trueValue), toExpression(falseValue));
}
@Override
protected Object visitNotExpression(NotExpression node, Object context)
{
Object value = process(node.getValue(), context);
if (value == null) {
return null;
}
if (value instanceof Expression) {
return new NotExpression(toExpression(value));
}
return !(Boolean) value;
}
@Override
protected Object visitLogicalBinaryExpression(LogicalBinaryExpression node, Object context)
{
Object left = process(node.getLeft(), context);
Object right = process(node.getRight(), context);
switch (node.getType()) {
case AND: {
// if either left or right is false, result is always false regardless of nulls
if (Boolean.FALSE.equals(left) || Boolean.TRUE.equals(right)) {
return left;
}
if (Boolean.FALSE.equals(right) || Boolean.TRUE.equals(left)) {
return right;
}
}
case OR: {
// if either left or right is true, result is always true regardless of nulls
if (Boolean.TRUE.equals(left) || Boolean.FALSE.equals(right)) {
return left;
}
if (Boolean.TRUE.equals(right) || Boolean.FALSE.equals(left)) {
return right;
}
}
}
if (left == null && right == null) {
return null;
}
return new LogicalBinaryExpression(node.getType(), toExpression(left), toExpression(right));
}
@Override
protected Object visitBooleanLiteral(BooleanLiteral node, Object context)
{
return node.equals(BooleanLiteral.TRUE_LITERAL);
}
@Override
protected Object visitFunctionCall(FunctionCall node, Object context)
{
// TODO: remove this huge hack
List<Type> argumentTypes = new ArrayList<>();
List<Object> argumentValues = new ArrayList<>();
for (Expression expression : node.getArguments()) {
Object value = process(expression, context);
if (value == null) {
return null;
}
Type type;
if (value instanceof Double) {
type = Type.DOUBLE;
}
else if (value instanceof Long) {
type = Type.BIGINT;
}
else if (value instanceof Slice) {
type = Type.VARCHAR;
}
else if (value instanceof Boolean) {
type = Type.BOOLEAN;
}
else if (value instanceof Expression) {
// TODO when we know the type of this expression, construct new FunctionCall node with optimized arguments
return node;
}
else {
throw new UnsupportedOperationException("Unhandled value type: " + value.getClass().getName());
}
argumentValues.add(value);
argumentTypes.add(type);
}
FunctionInfo function = metadata.getFunction(node.getName(), argumentTypes, false);
// do not optimize non-deterministic functions
if (optimize && !function.isDeterministic()) {
return new FunctionCall(node.getName(), node.getWindow().orNull(), node.isDistinct(), toExpressions(argumentValues));
}
MethodHandle handle = function.getScalarFunction();
if (handle.type().parameterCount() > 0 && handle.type().parameterType(0) == Session.class) {
handle = handle.bindTo(session);
}
try {
return handle.invokeWithArguments(argumentValues);
}
catch (Throwable throwable) {
Throwables.propagateIfInstanceOf(throwable, RuntimeException.class);
Throwables.propagateIfInstanceOf(throwable, Error.class);
throw new RuntimeException(throwable.getMessage(), throwable);
}
}
@Override
protected Object visitLikePredicate(LikePredicate node, Object context)
{
Object value = process(node.getValue(), context);
if (value == null) {
return null;
}
if (value instanceof Slice &&
node.getPattern() instanceof StringLiteral &&
(node.getEscape() instanceof StringLiteral || node.getEscape() == null)) {
// fast path when we know the pattern and escape are constant
return LikeUtils.regexMatches(getConstantPattern(node), (Slice) value);
}
Object pattern = process(node.getPattern(), context);
if (pattern == null) {
return null;
}
Object escape = null;
if (node.getEscape() != null) {
escape = process(node.getEscape(), context);
if (escape == null) {
return null;
}
}
if (value instanceof Slice &&
pattern instanceof Slice &&
(escape == null || escape instanceof Slice)) {
Regex regex = LikeUtils.likeToPattern((Slice) pattern, (Slice) escape);
return LikeUtils.regexMatches(regex, (Slice) value);
}
// if pattern is a constant without % or _ replace with a comparison
if (pattern instanceof Slice && escape == null) {
String stringPattern = ((Slice) pattern).toString(Charsets.UTF_8);
if (!stringPattern.contains("%") && !stringPattern.contains("_")) {
return new ComparisonExpression(ComparisonExpression.Type.EQUAL, toExpression(value), toExpression(pattern));
}
}
Expression optimizedEscape = null;
if (node.getEscape() != null) {
optimizedEscape = toExpression(escape);
}
return new LikePredicate(toExpression(value), toExpression(pattern), optimizedEscape);
}
private Regex getConstantPattern(LikePredicate node)
{
Regex result = likePatternCache.get(node);
if (result == null) {
StringLiteral pattern = (StringLiteral) node.getPattern();
StringLiteral escape = (StringLiteral) node.getEscape();
result = LikeUtils.likeToPattern(pattern.getSlice(), escape == null ? null : escape.getSlice());
likePatternCache.put(node, result);
}
return result;
}
@Override
@SuppressWarnings("fallthrough")
protected Object visitExtract(Extract node, Object context)
{
Object value = process(node.getExpression(), context);
if (value == null) {
return null;
}
if (value instanceof Expression) {
return new Extract(toExpression(value), node.getField());
}
long time = (long) value;
switch (node.getField()) {
case CENTURY:
return UnixTimeFunctions.century(time);
case YEAR:
return UnixTimeFunctions.year(time);
case QUARTER:
return UnixTimeFunctions.quarter(time);
case MONTH:
return UnixTimeFunctions.month(time);
case WEEK:
return UnixTimeFunctions.week(time);
case DAY:
case DAY_OF_MONTH:
return UnixTimeFunctions.day(time);
case DAY_OF_WEEK:
case DOW:
return UnixTimeFunctions.dayOfWeek(time);
case DAY_OF_YEAR:
case DOY:
return UnixTimeFunctions.dayOfYear(time);
case HOUR:
return UnixTimeFunctions.hour(time);
case MINUTE:
return UnixTimeFunctions.minute(time);
case SECOND:
return UnixTimeFunctions.second(time);
case TIMEZONE_HOUR:
case TIMEZONE_MINUTE:
return 0L; // we assume all times are UTC for now TODO
}
throw new UnsupportedOperationException("not yet implemented: " + node.getField());
}
@Override
public Object visitCast(Cast node, Object context)
{
Object value = process(node.getExpression(), context);
if (value instanceof Expression) {
return new Cast((Expression) value, node.getType());
}
if (value == null) {
return null;
}
switch (node.getType()) {
case "BOOLEAN":
return Casts.toBoolean(value);
case "VARCHAR":
return Casts.toSlice(value);
case "DOUBLE":
return Casts.toDouble(value);
case "BIGINT":
return Casts.toLong(value);
}
throw new UnsupportedOperationException("Unsupported type: " + node.getType());
}
@Override
protected Object visitExpression(Expression node, Object context)
{
throw new UnsupportedOperationException("not yet implemented: " + node.getClass().getName());
}
@Override
protected Object visitNode(Node node, Object context)
{
throw new UnsupportedOperationException("Evaluator visitor can only handle Expression nodes");
}
private Object optimize(Node node, Object context)
{
checkState(optimize, "not optimizing");
try {
return process(node, context);
}
catch (RuntimeException e) {
return node;
}
}
}
private static Predicate<Expression> isNonNullLiteralPredicate()
{
return new Predicate<Expression>()
{
@Override
public boolean apply(@Nullable Expression input)
{
return input instanceof Literal && !(input instanceof NullLiteral);
}
};
}
}