Package com.facebook.presto.sql.gen

Source Code of com.facebook.presto.sql.gen.ByteCodeExpressionVisitor$TypedWhenClause

/*
* 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.gen;

import com.facebook.presto.byteCode.Block;
import com.facebook.presto.byteCode.ByteCodeNode;
import com.facebook.presto.byteCode.CompilerContext;
import com.facebook.presto.byteCode.Variable;
import com.facebook.presto.byteCode.control.IfStatement;
import com.facebook.presto.byteCode.control.IfStatement.IfStatementBuilder;
import com.facebook.presto.byteCode.control.LookupSwitch.LookupSwitchBuilder;
import com.facebook.presto.byteCode.instruction.Constant;
import com.facebook.presto.byteCode.instruction.LabelNode;
import com.facebook.presto.spi.RecordCursor;
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.DoubleLiteral;
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.LogicalBinaryExpression;
import com.facebook.presto.sql.tree.LongLiteral;
import com.facebook.presto.sql.tree.NegativeExpression;
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.QualifiedName;
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.facebook.presto.util.IterableTransformer;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicates;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.primitives.Primitives;
import io.airlift.slice.Slice;
import io.airlift.slice.Slices;

import java.lang.invoke.ConstantCallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import static com.facebook.presto.byteCode.OpCodes.L2D;
import static com.facebook.presto.byteCode.OpCodes.NOP;
import static com.facebook.presto.byteCode.control.IfStatement.ifStatementBuilder;
import static com.facebook.presto.byteCode.control.LookupSwitch.lookupSwitchBuilder;
import static com.facebook.presto.byteCode.instruction.Constant.loadBoolean;
import static com.facebook.presto.byteCode.instruction.Constant.loadDouble;
import static com.facebook.presto.byteCode.instruction.Constant.loadLong;
import static com.facebook.presto.byteCode.instruction.JumpInstruction.jump;
import static com.facebook.presto.sql.gen.SliceConstant.sliceConstant;
import static com.facebook.presto.sql.gen.TypedByteCodeNode.typedByteCodeNode;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Predicates.not;
import static com.google.common.collect.Iterables.transform;
import static java.lang.String.format;
import static java.lang.invoke.MethodHandles.lookup;

public class ByteCodeExpressionVisitor
        extends AstVisitor<TypedByteCodeNode, CompilerContext>
{
    private final BootstrapFunctionBinder bootstrapFunctionBinder;
    private final Map<Input, Type> inputTypes;
    private final ByteCodeNode getSessionByteCode;
    private final boolean sourceIsCursor;

    public ByteCodeExpressionVisitor(BootstrapFunctionBinder bootstrapFunctionBinder, Map<Input, Type> inputTypes, ByteCodeNode getSessionByteCode, boolean sourceIsCursor)
    {
        this.bootstrapFunctionBinder = bootstrapFunctionBinder;
        this.inputTypes = inputTypes;
        this.getSessionByteCode = getSessionByteCode;
        this.sourceIsCursor = sourceIsCursor;
    }

    @Override
    protected TypedByteCodeNode visitBooleanLiteral(BooleanLiteral node, CompilerContext context)
    {
        return typedByteCodeNode(loadBoolean(node.getValue()), boolean.class);
    }

    @Override
    protected TypedByteCodeNode visitLongLiteral(LongLiteral node, CompilerContext context)
    {
        return typedByteCodeNode(loadLong(node.getValue()), long.class);
    }

    @Override
    protected TypedByteCodeNode visitDoubleLiteral(DoubleLiteral node, CompilerContext context)
    {
        return typedByteCodeNode(loadDouble(node.getValue()), double.class);
    }

    @Override
    protected TypedByteCodeNode visitStringLiteral(StringLiteral node, CompilerContext context)
    {
        return typedByteCodeNode(sliceConstant(node.getSlice()), Slice.class);
    }

    @Override
    protected TypedByteCodeNode visitNullLiteral(NullLiteral node, CompilerContext context)
    {
        // todo this should be the real type of the expression
        return typedByteCodeNode(new Block(context).putVariable("wasNull", true), void.class);
    }

    @Override
    public TypedByteCodeNode visitInputReference(InputReference node, CompilerContext context)
    {
        Input input = node.getInput();
        int channel = input.getChannel();
        Type type = inputTypes.get(input);
        checkState(type != null, "No type for input %s", input);

        if (sourceIsCursor) {
            Block isNullCheck = new Block(context)
                    .setDescription(format("cursor.get%s(%d)", type, channel))
                    .getVariable("cursor")
                    .push(channel)
                    .invokeInterface(RecordCursor.class, "isNull", boolean.class, int.class);

            switch (type) {
                case BOOLEAN: {
                    Block isNull = new Block(context)
                            .putVariable("wasNull", true)
                            .pushJavaDefault(boolean.class);

                    Block isNotNull = new Block(context)
                            .getVariable("cursor")
                            .push(channel)
                            .invokeInterface(RecordCursor.class, "getBoolean", boolean.class, int.class);

                    return typedByteCodeNode(new IfStatement(context, isNullCheck, isNull, isNotNull), boolean.class);
                }
                case BIGINT: {
                    Block isNull = new Block(context)
                            .putVariable("wasNull", true)
                            .pushJavaDefault(long.class);

                    Block isNotNull = new Block(context)
                            .getVariable("cursor")
                            .push(channel)
                            .invokeInterface(RecordCursor.class, "getLong", long.class, int.class);

                    return typedByteCodeNode(new IfStatement(context, isNullCheck, isNull, isNotNull), long.class);
                }
                case DOUBLE: {
                    Block isNull = new Block(context)
                            .putVariable("wasNull", true)
                            .pushJavaDefault(double.class);

                    Block isNotNull = new Block(context)
                            .getVariable("cursor")
                            .push(channel)
                            .invokeInterface(RecordCursor.class, "getDouble", double.class, int.class);

                    return typedByteCodeNode(new IfStatement(context, isNullCheck, isNull, isNotNull), double.class);
                }
                case VARCHAR: {
                    Block isNull = new Block(context)
                            .putVariable("wasNull", true)
                            .pushJavaDefault(Slice.class);

                    Block isNotNull = new Block(context)
                            .getVariable("cursor")
                            .push(channel)
                            .invokeInterface(RecordCursor.class, "getString", byte[].class, int.class)
                            .invokeStatic(Slices.class, "wrappedBuffer", Slice.class, byte[].class);

                    return typedByteCodeNode(new IfStatement(context, isNullCheck, isNull, isNotNull), Slice.class);
                }
                default:
                    throw new UnsupportedOperationException("not yet implemented: " + type);
            }
        }
        else {
            Block isNullCheck = new Block(context)
                    .setDescription(format("channel_%d.get%s()", channel, type))
                    .getVariable("channel_" + channel)
                    .invokeInterface(TupleReadable.class, "isNull", boolean.class);

            switch (type) {
                case BOOLEAN: {
                    Block isNull = new Block(context)
                            .putVariable("wasNull", true)
                            .pushJavaDefault(boolean.class);

                    Block isNotNull = new Block(context)
                            .getVariable("channel_" + channel)
                            .invokeInterface(TupleReadable.class, "getBoolean", boolean.class);

                    return typedByteCodeNode(new IfStatement(context, isNullCheck, isNull, isNotNull), boolean.class);
                }
                case BIGINT: {
                    Block isNull = new Block(context)
                            .putVariable("wasNull", true)
                            .pushJavaDefault(long.class);

                    Block isNotNull = new Block(context)
                            .getVariable("channel_" + channel)
                            .invokeInterface(TupleReadable.class, "getLong", long.class);

                    return typedByteCodeNode(new IfStatement(context, isNullCheck, isNull, isNotNull), long.class);
                }
                case DOUBLE: {
                    Block isNull = new Block(context)
                            .putVariable("wasNull", true)
                            .pushJavaDefault(double.class);

                    Block isNotNull = new Block(context)
                            .getVariable("channel_" + channel)
                            .invokeInterface(TupleReadable.class, "getDouble", double.class);

                    return typedByteCodeNode(new IfStatement(context, isNullCheck, isNull, isNotNull), double.class);
                }
                case VARCHAR: {
                    Block isNull = new Block(context)
                            .putVariable("wasNull", true)
                            .pushJavaDefault(Slice.class);

                    Block isNotNull = new Block(context)
                            .getVariable("channel_" + channel)
                            .invokeInterface(TupleReadable.class, "getSlice", Slice.class);

                    return typedByteCodeNode(new IfStatement(context, isNullCheck, isNull, isNotNull), Slice.class);
                }
                default:
                    throw new UnsupportedOperationException("not yet implemented: " + type);
            }
        }
    }

    @Override
    protected TypedByteCodeNode visitCurrentTime(CurrentTime node, CompilerContext context)
    {
        return visitFunctionCall(new FunctionCall(new QualifiedName("now"), ImmutableList.<Expression>of()), context);
    }

    @Override
    protected TypedByteCodeNode visitFunctionCall(FunctionCall node, CompilerContext context)
    {
        List<TypedByteCodeNode> arguments = new ArrayList<>();
        for (Expression argument : node.getArguments()) {
            TypedByteCodeNode typedByteCodeNode = process(argument, context);
            if (typedByteCodeNode.getType() == void.class) {
                return typedByteCodeNode;
            }
            arguments.add(typedByteCodeNode);
        }

        FunctionBinding functionBinding = bootstrapFunctionBinder.bindFunction(node.getName(), getSessionByteCode, arguments);
        return visitFunctionBinding(context, functionBinding, node.toString());
    }

    @Override
    protected TypedByteCodeNode visitExtract(Extract node, CompilerContext context)
    {
        TypedByteCodeNode expression = process(node.getExpression(), context);
        if (expression.getType() == void.class) {
            return expression;
        }

        if (node.getField() == Extract.Field.TIMEZONE_HOUR || node.getField() == Extract.Field.TIMEZONE_MINUTE) {
            // TODO: we assume all times are UTC for now
            return typedByteCodeNode(new Block(context).append(expression.getNode()).pop(long.class).push(0L), long.class);
        }

        QualifiedName functionName = QualifiedName.of(node.getField().name().toLowerCase());
        FunctionBinding functionBinding = bootstrapFunctionBinder.bindFunction(functionName, getSessionByteCode, ImmutableList.of(expression));
        return visitFunctionBinding(context, functionBinding, node.toString());
    }

    @Override
    protected TypedByteCodeNode visitLikePredicate(LikePredicate node, CompilerContext context)
    {
        ImmutableList<Expression> expressions;
        if (node.getEscape() != null) {
            expressions = ImmutableList.of(node.getValue(), node.getPattern(), node.getEscape());
        }
        else {
            expressions = ImmutableList.of(node.getValue(), node.getPattern());
        }

        List<TypedByteCodeNode> arguments = new ArrayList<>();
        for (Expression argument : expressions) {
            TypedByteCodeNode typedByteCodeNode = process(argument, context);
            if (typedByteCodeNode.getType() == void.class) {
                return typedByteCodeNode;
            }
            arguments.add(typedByteCodeNode);
        }

        FunctionBinding functionBinding = bootstrapFunctionBinder.bindFunction("like", getSessionByteCode, arguments, new LikeFunctionBinder());
        return visitFunctionBinding(context, functionBinding, node.toString());
    }

    private TypedByteCodeNode visitFunctionBinding(CompilerContext context, FunctionBinding functionBinding, String comment)
    {
        List<TypedByteCodeNode> arguments = functionBinding.getArguments();
        MethodType methodType = functionBinding.getCallSite().type();
        Class<?> unboxedReturnType = Primitives.unwrap(methodType.returnType());

        LabelNode end = new LabelNode("end");
        Block block = new Block(context)
                .setDescription("invoke")
                .comment(comment);
        ArrayList<Class<?>> stackTypes = new ArrayList<>();
        for (int i = 0; i < arguments.size(); i++) {
            TypedByteCodeNode argument = arguments.get(i);
            Class<?> argumentType = methodType.parameterList().get(i);
            block.append(coerceToType(context, argument, argumentType).getNode());

            stackTypes.add(argument.getType());
            block.append(ifWasNullPopAndGoto(context, end, unboxedReturnType, Lists.reverse(stackTypes)));
        }
        block.invokeDynamic(functionBinding.getName(), methodType, functionBinding.getBindingId());

        if (functionBinding.isNullable()) {
            if (unboxedReturnType.isPrimitive()) {
                LabelNode notNull = new LabelNode("notNull");
                block.dup(methodType.returnType())
                        .ifNotNullGoto(notNull)
                        .putVariable("wasNull", true)
                        .comment("swap boxed null with unboxed default")
                        .pop(methodType.returnType())
                        .pushJavaDefault(unboxedReturnType)
                        .gotoLabel(end)
                        .visitLabel(notNull)
                        .append(unboxPrimitive(context, unboxedReturnType));
            }
            else {
                block.dup(methodType.returnType())
                        .ifNotNullGoto(end)
                        .putVariable("wasNull", true);
            }
        }
        block.visitLabel(end);

        return typedByteCodeNode(block, unboxedReturnType);
    }

    private static ByteCodeNode unboxPrimitive(CompilerContext context, Class<?> unboxedType)
    {
        Block block = new Block(context).comment("unbox primitive");
        if (unboxedType == long.class) {
            return block.invokeVirtual(Long.class, "longValue", long.class);
        }
        if (unboxedType == double.class) {
            return block.invokeVirtual(Double.class, "doubleValue", double.class);
        }
        if (unboxedType == boolean.class) {
            return block.invokeVirtual(Boolean.class, "booleanValue", boolean.class);
        }
        throw new UnsupportedOperationException("not yet implemented: " + unboxedType);
    }

    @Override
    public TypedByteCodeNode visitCast(Cast node, CompilerContext context)
    {
        TypedByteCodeNode value = process(node.getExpression(), context);

        Block block = new Block(context).comment(node.toString());
        block.append(value.getNode());

        if (value.getType() == void.class) {
            switch (node.getType()) {
                case "BOOLEAN":
                    block.pushJavaDefault(boolean.class);
                    return typedByteCodeNode(block, boolean.class);
                case "BIGINT":
                    block.pushJavaDefault(long.class);
                    return typedByteCodeNode(block, long.class);
                case "DOUBLE":
                    block.pushJavaDefault(double.class);
                    return typedByteCodeNode(block, double.class);
                case "VARCHAR":
                    block.pushJavaDefault(Slice.class);
                    return typedByteCodeNode(block, Slice.class);
            }
        }
        else {
            LabelNode end = new LabelNode("end");
            switch (node.getType()) {
                case "BOOLEAN":
                    block.append(ifWasNullPopAndGoto(context, end, boolean.class, value.getType()));
                    block.invokeStatic(Operations.class, "castToBoolean", boolean.class, value.getType());
                    return typedByteCodeNode(block.visitLabel(end), boolean.class);
                case "BIGINT":
                    block.append(ifWasNullPopAndGoto(context, end, long.class, value.getType()));
                    block.invokeStatic(Operations.class, "castToLong", long.class, value.getType());
                    return typedByteCodeNode(block.visitLabel(end), long.class);
                case "DOUBLE":
                    block.append(ifWasNullPopAndGoto(context, end, double.class, value.getType()));
                    block.invokeStatic(Operations.class, "castToDouble", double.class, value.getType());
                    return typedByteCodeNode(block.visitLabel(end), double.class);
                case "VARCHAR":
                    block.append(ifWasNullPopAndGoto(context, end, Slice.class, value.getType()));
                    block.invokeStatic(Operations.class, "castToSlice", Slice.class, value.getType());
                    return typedByteCodeNode(block.visitLabel(end), Slice.class);
            }
        }
        throw new UnsupportedOperationException("Unsupported type: " + node.getType());
    }

    @Override
    protected TypedByteCodeNode visitArithmeticExpression(ArithmeticExpression node, CompilerContext context)
    {
        TypedByteCodeNode left = process(node.getLeft(), context);
        if (left.getType() == void.class) {
            return left;
        }

        TypedByteCodeNode right = process(node.getRight(), context);
        if (right.getType() == void.class) {
            return right;
        }

        Class<?> type = getType(left, right);
        if (!isNumber(type)) {
            throw new UnsupportedOperationException(format("not yet implemented: %s(%s, %s)", node.getType(), left.getType(), right.getType()));
        }

        Block block = new Block(context).comment(node.toString());
        LabelNode end = new LabelNode("end");

        block.append(coerceToType(context, left, type).getNode());
        block.append(ifWasNullPopAndGoto(context, end, type, left.getType()));

        block.append(coerceToType(context, right, type).getNode());
        block.append(ifWasNullPopAndGoto(context, end, type, type, right.getType()));

        switch (node.getType()) {
            case ADD:
                block.invokeStatic(Operations.class, "add", type, type, type);
                break;
            case SUBTRACT:
                block.invokeStatic(Operations.class, "subtract", type, type, type);
                break;
            case MULTIPLY:
                block.invokeStatic(Operations.class, "multiply", type, type, type);
                break;
            case DIVIDE:
                block.invokeStatic(Operations.class, "divide", type, type, type);
                break;
            case MODULUS:
                block.invokeStatic(Operations.class, "modulus", type, type, type);
                break;
            default:
                throw new UnsupportedOperationException(format("not yet implemented: %s(%s, %s)", node.getType(), left.getType(), right.getType()));
        }
        return typedByteCodeNode(block.visitLabel(end), type);
    }

    @Override
    protected TypedByteCodeNode visitNegativeExpression(NegativeExpression node, CompilerContext context)
    {
        TypedByteCodeNode value = process(node.getValue(), context);
        if (value.getType() == void.class) {
            return value;
        }

        if (!isNumber(value.getType())) {
            throw new UnsupportedOperationException(format("not yet implemented: negate(%s)", value.getType()));
        }

        // simple single op so there is no reason to do a null check
        Block block = new Block(context)
                .comment(node.toString())
                .append(value.getNode())
                .invokeStatic(Operations.class, "negate", value.getType(), value.getType());

        return typedByteCodeNode(block, value.getType());
    }

    @Override
    protected TypedByteCodeNode visitLogicalBinaryExpression(LogicalBinaryExpression node, CompilerContext context)
    {
        TypedByteCodeNode left = process(node.getLeft(), context);
        if (left.getType() == void.class) {
            left = coerceToType(context, left, boolean.class);
        }
        Preconditions.checkState(left.getType() == boolean.class, "Expected logical binary expression left value to be a boolean but is a %s: %s", left.getType().getName(), node);

        TypedByteCodeNode right = process(node.getRight(), context);
        if (right.getType() == void.class) {
            right = coerceToType(context, right, boolean.class);
        }
        Preconditions.checkState(right.getType() == boolean.class, "Expected logical binary expression right value to be a boolean but is a %s: %s", right.getType().getName(), node);

        switch (node.getType()) {
            case AND:
                return visitAnd(context, left, right, node.toString());
            case OR:
                return visitOr(context, left, right, node.toString());
        }
        throw new UnsupportedOperationException(format("not yet implemented: %s(%s, %s)", node.getType(), left.getType(), right.getType()));
    }

    private TypedByteCodeNode visitAnd(CompilerContext context, TypedByteCodeNode left, TypedByteCodeNode right, String comment)
    {
        Block block = new Block(context)
                .comment(comment)
                .setDescription("AND");

        block.append(left.getNode());

        IfStatementBuilder ifLeftIsNull = ifStatementBuilder(context)
                .comment("if left wasNull...")
                .condition(new Block(context).getVariable("wasNull"));

        LabelNode end = new LabelNode("end");
        ifLeftIsNull.ifTrue(new Block(context)
                .comment("clear the null flag, pop left value off stack, and push left null flag on the stack (true)")
                .putVariable("wasNull", false)
                .pop(left.getType()) // discard left value
                .push(true));

        LabelNode leftIsTrue = new LabelNode("leftIsTrue");
        ifLeftIsNull.ifFalse(new Block(context)
                .comment("if left is false, push false, and goto end")
                .ifTrueGoto(leftIsTrue)
                .push(false)
                .gotoLabel(end)
                .comment("left was true; push left null flag on the stack (false)")
                .visitLabel(leftIsTrue)
                .push(false));

        block.append(ifLeftIsNull.build());

        // At this point we know the left expression was either NULL or TRUE.  The stack contains a single boolean
        // value for this expression which indicates if the left value was NULL.

        // eval right!
        block.append(right.getNode());

        IfStatementBuilder ifRightIsNull = ifStatementBuilder(context)
                .comment("if right wasNull...")
                .condition(new Block(context).getVariable("wasNull"));

        // this leaves a single boolean on the stack which is ignored since the value in NULL
        ifRightIsNull.ifTrue(new Block(context)
                .comment("right was null, pop the right value off the stack; wasNull flag remains set to TRUE")
                .pop(right.getType()));

        LabelNode rightIsTrue = new LabelNode("rightIsTrue");
        ifRightIsNull.ifFalse(new Block(context)
                .comment("if right is false, pop left null flag off stack, push false and goto end")
                .ifTrueGoto(rightIsTrue)
                .pop(boolean.class)
                .push(false)
                .gotoLabel(end)
                .comment("right was true; store left null flag (on stack) in wasNull variable, and push true")
                .visitLabel(rightIsTrue)
                .putVariable("wasNull")
                .push(true));

        block.append(ifRightIsNull.build())
                .visitLabel(end);

        return typedByteCodeNode(block, boolean.class);
    }

    private TypedByteCodeNode visitOr(CompilerContext context, TypedByteCodeNode left, TypedByteCodeNode right, String comment)
    {
        Block block = new Block(context)
                .comment(comment)
                .setDescription("OR");

        block.append(left.getNode());

        IfStatementBuilder ifLeftIsNull = ifStatementBuilder(context)
                .comment("if left wasNull...")
                .condition(new Block(context).getVariable("wasNull"));

        LabelNode end = new LabelNode("end");
        ifLeftIsNull.ifTrue(new Block(context)
                .comment("clear the null flag, pop left value off stack, and push left null flag on the stack (true)")
                .putVariable("wasNull", false)
                .pop(left.getType()) // discard left value
                .push(true));

        LabelNode leftIsFalse = new LabelNode("leftIsFalse");
        ifLeftIsNull.ifFalse(new Block(context)
                .comment("if left is true, push true, and goto end")
                .ifFalseGoto(leftIsFalse)
                .push(true)
                .gotoLabel(end)
                .comment("left was false; push left null flag on the stack (false)")
                .visitLabel(leftIsFalse)
                .push(false));

        block.append(ifLeftIsNull.build());

        // At this point we know the left expression was either NULL or FALSE.  The stack contains a single boolean
        // value for this expression which indicates if the left value was NULL.

        // eval right!
        block.append(right.getNode());

        IfStatementBuilder ifRightIsNull = ifStatementBuilder(context)
                .comment("if right wasNull...")
                .condition(new Block(context).getVariable("wasNull"));

        // this leaves a single boolean on the stack which is ignored since the value in NULL
        ifRightIsNull.ifTrue(new Block(context)
                .comment("right was null, pop the right value off the stack; wasNull flag remains set to TRUE")
                .pop(right.getType()));

        LabelNode rightIsTrue = new LabelNode("rightIsTrue");
        ifRightIsNull.ifFalse(new Block(context)
                .comment("if right is true, pop left null flag off stack, push true and goto end")
                .ifFalseGoto(rightIsTrue)
                .pop(boolean.class)
                .push(true)
                .gotoLabel(end)
                .comment("right was false; store left null flag (on stack) in wasNull variable, and push false")
                .visitLabel(rightIsTrue)
                .putVariable("wasNull")
                .push(false));

        block.append(ifRightIsNull.build())
                .visitLabel(end);

        return typedByteCodeNode(block, boolean.class);
    }

    @Override
    protected TypedByteCodeNode visitNotExpression(NotExpression node, CompilerContext context)
    {
        TypedByteCodeNode value = process(node.getValue(), context);
        if (value.getType() == void.class) {
            return value;
        }

        Preconditions.checkState(value.getType() == boolean.class);
        // simple single op so there is no reason to do a null check
        return typedByteCodeNode(new Block(context)
                .comment(node.toString())
                .append(value.getNode())
                .invokeStatic(Operations.class, "not", boolean.class, boolean.class), boolean.class);
    }

    @Override
    protected TypedByteCodeNode visitComparisonExpression(ComparisonExpression node, CompilerContext context)
    {
        // distinct requires special null handling rules
        if (node.getType() == ComparisonExpression.Type.IS_DISTINCT_FROM) {
            return visitIsDistinctFrom(node, context);
        }

        TypedByteCodeNode left = process(node.getLeft(), context);
        if (left.getType() == void.class) {
            return left;
        }

        TypedByteCodeNode right = process(node.getRight(), context);
        if (right.getType() == void.class) {
            return right;
        }

        Class<?> type = getType(left, right);

        String function;
        switch (node.getType()) {
            case EQUAL:
                function = "equal";
                break;
            case NOT_EQUAL:
                function = "notEqual";
                break;
            case LESS_THAN:
                checkArgument(type != boolean.class, "not yet implemented: %s(%s, %s)", node.getType(), left.getType(), right.getType());
                function = "lessThan";
                break;
            case LESS_THAN_OR_EQUAL:
                checkArgument(type != boolean.class, "not yet implemented: %s(%s, %s)", node.getType(), left.getType(), right.getType());
                function = "lessThanOrEqual";
                break;
            case GREATER_THAN:
                checkArgument(type != boolean.class, "not yet implemented: %s(%s, %s)", node.getType(), left.getType(), right.getType());
                function = "greaterThan";
                break;
            case GREATER_THAN_OR_EQUAL:
                checkArgument(type != boolean.class, "not yet implemented: %s(%s, %s)", node.getType(), left.getType(), right.getType());
                function = "greaterThanOrEqual";
                break;
            default:
                throw new UnsupportedOperationException(format("not yet implemented: %s(%s, %s)", node.getType(), left.getType(), right.getType()));
        }

        LabelNode end = new LabelNode("end");
        Block block = new Block(context)
                .comment(node.toString());

        block.append(coerceToType(context, left, type).getNode());
        block.append(ifWasNullPopAndGoto(context, end, boolean.class, left.getType()));

        block.append(coerceToType(context, right, type).getNode());
        block.append(ifWasNullPopAndGoto(context, end, boolean.class, type, right.getType()));

        block.invokeStatic(Operations.class, function, boolean.class, type, type);
        return typedByteCodeNode(block.visitLabel(end), boolean.class);
    }

    private TypedByteCodeNode visitIsDistinctFrom(ComparisonExpression node, CompilerContext context)
    {
        TypedByteCodeNode left = process(node.getLeft(), context);
        TypedByteCodeNode right = process(node.getRight(), context);

        Class<?> type = getType(left, right);
        if (type == void.class) {
            // both left and right are literal nulls, which are not "distinct from" each other
            return typedByteCodeNode(loadBoolean(false), boolean.class);
        }

        Block block = new Block(context)
                .comment(node.toString())
                .comment("left")
                .append(coerceToType(context, left, type).getNode())
                .getVariable("wasNull")
                .comment("clear was null")
                .putVariable("wasNull", false)
                .comment("right")
                .append(coerceToType(context, right, type).getNode())
                .getVariable("wasNull")
                .comment("clear was null")
                .putVariable("wasNull", false)
                .invokeStatic(Operations.class, "isDistinctFrom", boolean.class, type, boolean.class, type, boolean.class);

        return typedByteCodeNode(block, boolean.class);
    }

    @Override
    protected TypedByteCodeNode visitBetweenPredicate(BetweenPredicate node, CompilerContext context)
    {
        TypedByteCodeNode value = process(node.getValue(), context);
        if (value.getType() == void.class) {
            return value;
        }

        TypedByteCodeNode min = process(node.getMin(), context);
        if (min.getType() == void.class) {
            return min;
        }

        TypedByteCodeNode max = process(node.getMax(), context);
        if (max.getType() == void.class) {
            return max;
        }

        Class<?> type = getType(value, min, max);

        LabelNode end = new LabelNode("end");
        Block block = new Block(context)
                .comment(node.toString());

        block.append(coerceToType(context, value, type).getNode());
        block.append(ifWasNullPopAndGoto(context, end, boolean.class, type));

        block.append(coerceToType(context, min, type).getNode());
        block.append(ifWasNullPopAndGoto(context, end, boolean.class, type, type));

        block.append(coerceToType(context, max, type).getNode());
        block.append(ifWasNullPopAndGoto(context, end, boolean.class, type, type, type));

        block.invokeStatic(Operations.class, "between", boolean.class, type, type, type);
        return typedByteCodeNode(block.visitLabel(end), boolean.class);
    }

    @Override
    protected TypedByteCodeNode visitIsNotNullPredicate(IsNotNullPredicate node, CompilerContext context)
    {
        TypedByteCodeNode value = process(node.getValue(), context);
        if (value.getType() == void.class) {
            return typedByteCodeNode(loadBoolean(false), boolean.class);
        }

        // evaluate the expression, pop the produced value, load the null flag, and invert it
        Block block = new Block(context)
                .comment(node.toString())
                .append(value.getNode())
                .pop(value.getType())
                .getVariable("wasNull")
                .invokeStatic(Operations.class, "not", boolean.class, boolean.class);

        // clear the null flag
        block.putVariable("wasNull", false);

        return typedByteCodeNode(block, boolean.class);
    }

    @Override
    protected TypedByteCodeNode visitIsNullPredicate(IsNullPredicate node, CompilerContext context)
    {
        TypedByteCodeNode value = process(node.getValue(), context);
        if (value.getType() == void.class) {
            return typedByteCodeNode(loadBoolean(true), boolean.class);
        }

        // evaluate the expression, pop the produced value, and load the null flag
        Block block = new Block(context)
                .comment(node.toString())
                .append(value.getNode())
                .pop(value.getType())
                .getVariable("wasNull");

        // clear the null flag
        block.putVariable("wasNull", false);

        return typedByteCodeNode(block, boolean.class);
    }

    @Override
    protected TypedByteCodeNode visitIfExpression(IfExpression node, CompilerContext context)
    {
        TypedByteCodeNode conditionValue = process(node.getCondition(), context);
        TypedByteCodeNode trueValue = process(node.getTrueValue(), context);
        TypedByteCodeNode falseValue = process(node.getFalseValue().or(new NullLiteral()), context);

        if (conditionValue.getType() == void.class) {
            return falseValue;
        }
        Preconditions.checkState(conditionValue.getType() == boolean.class);

        // if conditionValue and conditionValue was not null
        Block condition = new Block(context)
                .comment(node.toString())
                .append(conditionValue.getNode())
                .comment("... and condition value was not null")
                .getVariable("wasNull")
                .invokeStatic(Operations.class, "not", boolean.class, boolean.class)
                .invokeStatic(Operations.class, "and", boolean.class, boolean.class, boolean.class)
                .putVariable("wasNull", false);

        Class<?> type = getType(trueValue, falseValue);
        if (type == void.class) {
            // both true and false are null literal
            return trueValue;
        }

        trueValue = coerceToType(context, trueValue, type);
        falseValue = coerceToType(context, falseValue, type);

        return typedByteCodeNode(new IfStatement(context, condition, trueValue.getNode(), falseValue.getNode()), type);
    }

    @Override
    protected TypedByteCodeNode visitSearchedCaseExpression(SearchedCaseExpression node, final CompilerContext context)
    {
        TypedByteCodeNode elseValue;
        if (node.getDefaultValue() != null) {
            elseValue = process(node.getDefaultValue(), context);
        }
        else {
            elseValue = process(new NullLiteral(), context);
        }

        List<TypedWhenClause> whenClauses = ImmutableList.copyOf(transform(node.getWhenClauses(), new Function<WhenClause, TypedWhenClause>()
        {
            @Override
            public TypedWhenClause apply(WhenClause whenClause)
            {
                return new TypedWhenClause(context, whenClause);
            }
        }));

        Class<?> type = getType(ImmutableList.<TypedByteCodeNode>builder().addAll(transform(whenClauses, whenValueGetter())).add(elseValue).build());

        elseValue = coerceToType(context, elseValue, type);
        // reverse list because current if statement builder doesn't support if/else so we need to build the if statements bottom up
        for (TypedWhenClause whenClause : Lists.reverse(new ArrayList<>(whenClauses))) {
            if (whenClause.condition.getType() == void.class) {
                continue;
            }
            Preconditions.checkState(whenClause.condition.getType() == boolean.class);

            // if conditionValue and conditionValue was not null
            Block condition = new Block(context)
                    .append(whenClause.condition.getNode())
                    .comment("... and condition value was not null")
                    .getVariable("wasNull")
                    .invokeStatic(Operations.class, "not", boolean.class, boolean.class)
                    .invokeStatic(Operations.class, "and", boolean.class, boolean.class, boolean.class)
                    .putVariable("wasNull", false);

            elseValue = typedByteCodeNode(new IfStatement(context, condition, coerceToType(context, whenClause.value, type).getNode(), elseValue.getNode()), type);
        }

        return elseValue;
    }

    @Override
    protected TypedByteCodeNode visitSimpleCaseExpression(SimpleCaseExpression node, final CompilerContext context)
    {
        // process value, else, and all when clauses
        TypedByteCodeNode value = process(node.getOperand(), context);
        TypedByteCodeNode elseValue;
        if (node.getDefaultValue() != null) {
            elseValue = process(node.getDefaultValue(), context);
        }
        else {
            elseValue = process(new NullLiteral(), context);
        }
        List<TypedWhenClause> whenClauses = ImmutableList.copyOf(transform(node.getWhenClauses(), new Function<WhenClause, TypedWhenClause>()
        {
            @Override
            public TypedWhenClause apply(WhenClause whenClause)
            {
                return new TypedWhenClause(context, whenClause);
            }
        }));

        // determine the type of the value and result
        Class<?> valueType = getType(ImmutableList.<TypedByteCodeNode>builder().addAll(transform(whenClauses, whenConditionGetter())).add(value).build());
        Class<?> resultType = getType(ImmutableList.<TypedByteCodeNode>builder().addAll(transform(whenClauses, whenValueGetter())).add(elseValue).build());

        if (value.getType() == void.class) {
            return coerceToType(context, elseValue, resultType);
        }

        // evaluate the value and store it in a variable
        LabelNode nullValue = new LabelNode("nullCondition");
        Variable tempVariable = context.createTempVariable(valueType);
        Block block = new Block(context)
                .append(coerceToType(context, value, valueType).getNode())
                .append(ifWasNullClearPopAndGoto(context, nullValue, void.class, valueType))
                .putVariable(tempVariable.getLocalVariableDefinition());

        // build the statements
        elseValue = typedByteCodeNode(new Block(context).visitLabel(nullValue).append(coerceToType(context, elseValue, resultType).getNode()), resultType);
        // reverse list because current if statement builder doesn't support if/else so we need to build the if statements bottom up
        for (TypedWhenClause whenClause : Lists.reverse(new ArrayList<>(whenClauses))) {
            LabelNode nullCondition = new LabelNode("nullCondition");
            Block condition = new Block(context)
                    .append(coerceToType(context, whenClause.condition, valueType).getNode())
                    .append(ifWasNullPopAndGoto(context, nullCondition, boolean.class, valueType))
                    .getVariable(tempVariable.getLocalVariableDefinition())
                    .invokeStatic(Operations.class, "equal", boolean.class, valueType, valueType)
                    .visitLabel(nullCondition)
                    .putVariable("wasNull", false);

            elseValue = typedByteCodeNode(new IfStatement(context,
                    format("when %s", whenClause),
                    condition,
                    coerceToType(context, whenClause.value, resultType).getNode(),
                    elseValue.getNode()), resultType);
        }

        return typedByteCodeNode(block.append(elseValue.getNode()), resultType);
    }

    @Override
    protected TypedByteCodeNode visitNullIfExpression(NullIfExpression node, CompilerContext context)
    {
        TypedByteCodeNode first = process(node.getFirst(), context);
        TypedByteCodeNode second = process(node.getSecond(), context);
        if (first.getType() == void.class) {
            return first;
        }

        Class<?> comparisonType = getType(first, second);

        LabelNode notMatch = new LabelNode("notMatch");
        Block block = new Block(context)
                .comment(node.toString())
                .append(first.getNode())
                .append(ifWasNullPopAndGoto(context, notMatch, void.class))
                .append(coerceToType(context, typedByteCodeNode(new Block(context).dup(first.getType()), first.getType()), comparisonType).getNode())
                .append(coerceToType(context, second, comparisonType).getNode())
                .append(ifWasNullClearPopAndGoto(context, notMatch, void.class, comparisonType, comparisonType));

        Block conditionBlock = new Block(context)
                .invokeStatic(Operations.class, "equal", boolean.class, comparisonType, comparisonType);

        Block trueBlock = new Block(context)
                .putVariable("wasNull", true)
                .pop(first.getType())
                .pushJavaDefault(first.getType());

        block.append(new IfStatement(context, conditionBlock, trueBlock, notMatch));

        return typedByteCodeNode(block, first.getType());
    }

    @Override
    protected TypedByteCodeNode visitCoalesceExpression(CoalesceExpression node, CompilerContext context)
    {
        List<TypedByteCodeNode> operands = new ArrayList<>();
        for (Expression expression : node.getOperands()) {
            operands.add(process(expression, context));
        }

        Class<?> type = getType(operands);

        TypedByteCodeNode nullValue = coerceToType(context, process(new NullLiteral(), context), type);
        // reverse list because current if statement builder doesn't support if/else so we need to build the if statements bottom up
        for (TypedByteCodeNode operand : Lists.reverse(operands)) {
            Block condition = new Block(context)
                    .append(coerceToType(context, operand, type).getNode())
                    .getVariable("wasNull");

            // if value was null, pop the null value, clear the null flag, and process the next operand
            Block nullBlock = new Block(context)
                    .pop(type)
                    .putVariable("wasNull", false)
                    .append(nullValue.getNode());

            nullValue = typedByteCodeNode(new IfStatement(context, condition, nullBlock, NOP), type);
        }

        return typedByteCodeNode(nullValue.getNode(), type);
    }

    @Override
    protected TypedByteCodeNode visitInPredicate(InPredicate node, CompilerContext context)
    {
        Expression valueListExpression = node.getValueList();
        if (!(valueListExpression instanceof InListExpression)) {
            throw new UnsupportedOperationException("Compilation of IN subquery is not supported yet");
        }

        TypedByteCodeNode value = process(node.getValue(), context);
        if (value.getType() == void.class) {
            return value;
        }

        ImmutableList.Builder<TypedByteCodeNode> values = ImmutableList.builder();
        InListExpression valueList = (InListExpression) valueListExpression;
        for (Expression test : valueList.getValues()) {
            TypedByteCodeNode testNode = process(test, context);
            values.add(testNode);
        }

        Class<?> type = getType(ImmutableList.<TypedByteCodeNode>builder()
                .add(value)
                .addAll(values.build()).build());

        ImmutableListMultimap.Builder<Integer, TypedByteCodeNode> hashBucketsBuilder = ImmutableListMultimap.builder();
        ImmutableList.Builder<TypedByteCodeNode> defaultBucket = ImmutableList.builder();
        ImmutableSet.Builder<Object> constantValuesBuilder = ImmutableSet.builder();
        for (TypedByteCodeNode testNode : values.build()) {
            if (testNode.getNode() instanceof Constant) {
                Constant constant = (Constant) testNode.getNode();
                Object testValue = constant.getValue();
                constantValuesBuilder.add(testValue);
                int hashCode;
                if (type == boolean.class) {
                    // boolean constant is actually an integer type
                    hashCode = Operations.hashCode(((Number) testValue).intValue() != 0);
                }
                else if (type == long.class) {
                    hashCode = Operations.hashCode((long) testValue);
                }
                else if (type == double.class) {
                    hashCode = Operations.hashCode(((Number) testValue).doubleValue());
                }
                else if (type == Slice.class) {
                    hashCode = Operations.hashCode((Slice) testValue);
                }
                else {
                    // SQL nulls are not currently encoded as constants, if they are one day, this code will need to be modified
                    throw new IllegalStateException("Error processing in statement: unsupported type " + testValue.getClass().getSimpleName());
                }

                hashBucketsBuilder.put(hashCode, coerceToType(context, testNode, type));
            }
            else {
                defaultBucket.add(coerceToType(context, testNode, type));
            }
        }
        ImmutableListMultimap<Integer, TypedByteCodeNode> hashBuckets = hashBucketsBuilder.build();
        ImmutableSet<Object> constantValues = constantValuesBuilder.build();

        LabelNode end = new LabelNode("end");
        LabelNode match = new LabelNode("match");
        LabelNode noMatch = new LabelNode("noMatch");

        LabelNode defaultLabel = new LabelNode("default");

        ByteCodeNode switchBlock;
        if (constantValues.size() < 1000) {
            Block switchCaseBlocks = new Block(context);
            LookupSwitchBuilder switchBuilder = lookupSwitchBuilder();
            for (Entry<Integer, Collection<TypedByteCodeNode>> bucket : hashBuckets.asMap().entrySet()) {
                LabelNode label = new LabelNode("inHash" + bucket.getKey());
                switchBuilder.addCase(bucket.getKey(), label);
                Collection<TypedByteCodeNode> testValues = bucket.getValue();

                Block caseBlock = buildInCase(context, type, label, match, defaultLabel, testValues, false);
                switchCaseBlocks
                        .append(caseBlock.setDescription("case " + bucket.getKey()));
            }
            switchBuilder.defaultCase(defaultLabel);

            switchBlock = new Block(context)
                    .comment("lookupSwitch(hashCode(<stackValue>))")
                    .dup(type)
                    .invokeStatic(Operations.class, "hashCode", int.class, type)
                    .append(switchBuilder.build())
                    .append(switchCaseBlocks);
        }
        else {
            // for huge IN lists, use a Set
            FunctionBinding functionBinding = bootstrapFunctionBinder.bindFunction(
                    "in",
                    getSessionByteCode,
                    ImmutableList.<TypedByteCodeNode>of(),
                    new InFunctionBinder(type, constantValues));

            switchBlock = new Block(context)
                    .comment("inListSet.contains(<stackValue>)")
                    .append(new IfStatement(context,
                            new Block(context).dup(type).invokeDynamic(functionBinding.getName(), functionBinding.getCallSite().type(), functionBinding.getBindingId()),
                            jump(match),
                            NOP));
        }

        Block defaultCaseBlock = buildInCase(context, type, defaultLabel, match, noMatch, defaultBucket.build(), true).setDescription("default");

        Block block = new Block(context)
                .comment(node.toString())
                .append(coerceToType(context, value, type).getNode())
                .append(ifWasNullPopAndGoto(context, end, boolean.class, type))
                .append(switchBlock)
                .append(defaultCaseBlock);

        Block matchBlock = new Block(context)
                .setDescription("match")
                .visitLabel(match)
                .pop(type)
                .putVariable("wasNull", false)
                .push(true)
                .gotoLabel(end);
        block.append(matchBlock);

        Block noMatchBlock = new Block(context)
                .setDescription("noMatch")
                .visitLabel(noMatch)
                .pop(type)
                .push(false)
                .gotoLabel(end);
        block.append(noMatchBlock);

        block.visitLabel(end);

        return typedByteCodeNode(block, boolean.class);
    }

    private Block buildInCase(CompilerContext context,
            Class<?> type,
            LabelNode caseLabel,
            LabelNode matchLabel,
            LabelNode noMatchLabel,
            Collection<TypedByteCodeNode> testValues,
            boolean checkForNulls)
    {
        Variable caseWasNull = null;
        if (checkForNulls) {
            caseWasNull = context.createTempVariable(boolean.class);
        }

        Block caseBlock = new Block(context)
                .visitLabel(caseLabel);

        if (checkForNulls) {
            caseBlock.putVariable(caseWasNull.getLocalVariableDefinition(), false);
        }

        LabelNode elseLabel = new LabelNode("else");
        Block elseBlock = new Block(context)
                .visitLabel(elseLabel);

        if (checkForNulls) {
            elseBlock.getVariable(caseWasNull.getLocalVariableDefinition())
                    .putVariable("wasNull");
        }

        elseBlock.gotoLabel(noMatchLabel);

        ByteCodeNode elseNode = elseBlock;
        for (TypedByteCodeNode testNode : testValues) {
            LabelNode testLabel = new LabelNode("test");
            IfStatementBuilder test = ifStatementBuilder(context);

            Block condition = new Block(context)
                    .visitLabel(testLabel)
                    .dup(type)
                    .append(coerceToType(context, testNode, type).getNode());

            if (checkForNulls) {
                condition.getVariable("wasNull")
                        .putVariable(caseWasNull.getLocalVariableDefinition())
                        .append(ifWasNullPopAndGoto(context, elseLabel, void.class, type, type));
            }
            condition.invokeStatic(Operations.class, "equal", boolean.class, type, type);
            test.condition(condition);

            test.ifTrue(new Block(context).gotoLabel(matchLabel));
            test.ifFalse(elseNode);

            elseNode = test.build();
            elseLabel = testLabel;
        }
        caseBlock.append(elseNode);
        return caseBlock;
    }

    @Override
    protected TypedByteCodeNode visitExpression(Expression node, CompilerContext context)
    {
        throw new UnsupportedOperationException(format("Compilation of %s not supported yet", node.getClass().getSimpleName()));
    }

    private ByteCodeNode ifWasNullPopAndGoto(CompilerContext context, LabelNode label, Class<?> returnType, Class<?>... stackArgsToPop)
    {
        return handleNullValue(context, label, returnType, ImmutableList.copyOf(stackArgsToPop), false);
    }

    private ByteCodeNode ifWasNullPopAndGoto(CompilerContext context, LabelNode label, Class<?> returnType, Iterable<? extends Class<?>> stackArgsToPop)
    {
        return handleNullValue(context, label, returnType, ImmutableList.copyOf(stackArgsToPop), false);
    }

    private ByteCodeNode ifWasNullClearPopAndGoto(CompilerContext context, LabelNode label, Class<?> returnType, Class<?>... stackArgsToPop)
    {
        return handleNullValue(context, label, returnType, ImmutableList.copyOf(stackArgsToPop), true);
    }

    private ByteCodeNode handleNullValue(CompilerContext context,
            LabelNode label,
            Class<?> returnType,
            List<? extends Class<?>> stackArgsToPop,
            boolean clearNullFlag)
    {
        Block nullCheck = new Block(context)
                .setDescription("ifWasNullGoto")
                .getVariable("wasNull");

        String clearComment = null;
        if (clearNullFlag) {
            nullCheck.putVariable("wasNull", false);
            clearComment = "clear wasNull";
        }

        Block isNull = new Block(context);
        for (Class<?> parameterType : stackArgsToPop) {
            isNull.pop(parameterType);
        }

        isNull.pushJavaDefault(returnType);
        String loadDefaultComment = null;
        if (returnType != void.class) {
            loadDefaultComment = format("loadJavaDefault(%s)", returnType.getName());
        }

        isNull.gotoLabel(label);

        String popComment = null;
        if (!stackArgsToPop.isEmpty()) {
            popComment = format("pop(%s)", Joiner.on(", ").join(stackArgsToPop));
        }

        String comment = format("if wasNull then %s", Joiner.on(", ").skipNulls().join(clearComment, popComment, loadDefaultComment, "goto " + label.getLabel()));
        return new IfStatement(context, comment, nullCheck, isNull, NOP);
    }

    private TypedByteCodeNode coerceToType(CompilerContext context, TypedByteCodeNode node, Class<?> type)
    {
        if (node.getType() == void.class) {
            return typedByteCodeNode(new Block(context).append(node.getNode()).pushJavaDefault(type), type);
        }
        if (node.getType() == long.class && type == double.class) {
            return typedByteCodeNode(new Block(context).append(node.getNode()).append(L2D), type);
        }
        return node;
    }

    private Class<?> getType(TypedByteCodeNode... nodes)
    {
        return getType(ImmutableList.copyOf(nodes));
    }

    private Class<?> getType(Iterable<TypedByteCodeNode> nodes)
    {
        Set<Class<?>> types = IterableTransformer.on(nodes)
                .transform(nodeTypeGetter())
                .select(not(Predicates.<Class<?>>equalTo(void.class)))
                .set();

        if (types.isEmpty()) {
            return void.class;
        }
        if (types.equals(ImmutableSet.of(double.class, long.class))) {
            return double.class;
        }
        checkState(types.size() == 1, "Expected only one type but found %s", types);
        return Iterables.getOnlyElement(types);
    }

    private static boolean isNumber(Class<?> type)
    {
        return type == long.class || type == double.class;
    }

    private static Function<TypedByteCodeNode, Class<?>> nodeTypeGetter()
    {
        return new Function<TypedByteCodeNode, Class<?>>()
        {
            @Override
            public Class<?> apply(TypedByteCodeNode node)
            {
                return node.getType();
            }
        };
    }

    private static Function<TypedWhenClause, TypedByteCodeNode> whenConditionGetter()
    {
        return new Function<TypedWhenClause, TypedByteCodeNode>()
        {
            @Override
            public TypedByteCodeNode apply(TypedWhenClause when)
            {
                return when.condition;
            }
        };
    }

    private static Function<TypedWhenClause, TypedByteCodeNode> whenValueGetter()
    {
        return new Function<TypedWhenClause, TypedByteCodeNode>()
        {
            @Override
            public TypedByteCodeNode apply(TypedWhenClause when)
            {
                return when.value;
            }
        };
    }

    public static class InFunctionBinder
            implements FunctionBinder
    {
        private static final MethodHandle inMethod;

        static {
            try {
                inMethod = lookup().findStatic(InFunctionBinder.class, "in", MethodType.methodType(boolean.class, ImmutableSet.class, Object.class));
            }
            catch (ReflectiveOperationException e) {
                throw Throwables.propagate(e);
            }
        }

        private final Class<?> valueType;
        private final ImmutableSet<Object> constantValues;

        public InFunctionBinder(Class<?> valueType, ImmutableSet<Object> constantValues)
        {
            this.valueType = valueType;
            this.constantValues = constantValues;
        }

        @Override
        public FunctionBinding bindFunction(long bindingId, String name, ByteCodeNode getSessionByteCode, List<TypedByteCodeNode> arguments)
        {
            MethodHandle methodHandle = inMethod.bindTo(constantValues);
            methodHandle = methodHandle.asType(MethodType.methodType(boolean.class, valueType));
            return new FunctionBinding(bindingId, name, new ConstantCallSite(methodHandle), arguments, false);
        }

        public static boolean in(ImmutableSet<?> set, Object value)
        {
            return set.contains(value);
        }
    }

    private class TypedWhenClause
    {
        private final TypedByteCodeNode condition;
        private final TypedByteCodeNode value;

        private TypedWhenClause(CompilerContext context, WhenClause whenClause)
        {
            this.condition = process(whenClause.getOperand(), context);
            this.value = process(whenClause.getResult(), context);
        }
    }
}
TOP

Related Classes of com.facebook.presto.sql.gen.ByteCodeExpressionVisitor$TypedWhenClause

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.