private static void generateFilterPageMethod(
      ClassDefinition classDefinition, RowExpression filter) {
    Parameter session = arg("session", ConnectorSession.class);
    Parameter page = arg("page", Page.class);

    MethodDefinition method =
        classDefinition.declareMethod(a(PUBLIC), "filterPage", type(int[].class), session, page);
    method.comment("Filter: %s rows in the page", filter.toString());

    Scope scope = method.getScope();
    Variable thisVariable = method.getThis();
    BytecodeBlock body = method.getBody();

    Variable positionCount =
        scope.declareVariable("positionCount", body, page.invoke("getPositionCount", int.class));
    Variable selectedPositions =
        scope.declareVariable(
            "selectedPositions", body, newArray(type(int[].class), positionCount));

    List<Integer> filterChannels = getInputChannels(filter);

    // extract block variables
    ImmutableList.Builder<Variable> blockVariablesBuilder = ImmutableList.<Variable>builder();
    for (int channel : filterChannels) {
      Variable blockVariable =
          scope.declareVariable(
              "block_" + channel, body, page.invoke("getBlock", Block.class, constantInt(channel)));
      blockVariablesBuilder.add(blockVariable);
    }
    List<Variable> blockVariables = blockVariablesBuilder.build();

    Variable selectedCount = scope.declareVariable("selectedCount", body, constantInt(0));
    Variable position = scope.declareVariable(int.class, "position");

    IfStatement ifStatement = new IfStatement();
    ifStatement
        .condition(invokeFilter(thisVariable, session, blockVariables, position))
        .ifTrue()
        .append(selectedPositions.setElement(selectedCount, position))
        .append(selectedCount.increment());

    body.append(
        new ForLoop()
            .initialize(position.set(constantInt(0)))
            .condition(lessThan(position, positionCount))
            .update(position.increment())
            .body(ifStatement));

    body.append(
        invokeStatic(Arrays.class, "copyOf", int[].class, selectedPositions, selectedCount).ret());
  }
  private static void generateGetNonLazyPageMethod(
      ClassDefinition classDefinition, RowExpression filter, List<RowExpression> projections) {
    Parameter page = arg("page", Page.class);
    MethodDefinition method =
        classDefinition.declareMethod(a(PRIVATE), "getNonLazyPage", type(Page.class), page);

    Scope scope = method.getScope();
    BytecodeBlock body = method.getBody();

    List<Integer> allInputChannels =
        getInputChannels(concat(projections, ImmutableList.of(filter)));
    if (allInputChannels.isEmpty()) {
      body.append(page.ret());
      return;
    }

    Variable index = scope.declareVariable(int.class, "index");
    Variable channelCount =
        scope.declareVariable("channelCount", body, page.invoke("getChannelCount", int.class));
    Variable blocks =
        scope.declareVariable("blocks", body, newArray(type(Block[].class), channelCount));
    Variable inputBlock = scope.declareVariable(Block.class, "inputBlock");

    Variable positionCount =
        scope.declareVariable("positionCount", body, page.invoke("getPositionCount", int.class));
    Variable createNewPage = scope.declareVariable("createNewPage", body, constantFalse());

    ForLoop forLoop =
        new ForLoop()
            .initialize(index.set(constantInt(0)))
            .condition(lessThan(index, channelCount))
            .update(index.increment())
            .body(
                new BytecodeBlock()
                    .append(inputBlock.set(page.invoke("getBlock", Block.class, index)))
                    .append(
                        new IfStatement()
                            .condition(inputBlock.instanceOf(LazyBlock.class))
                            .ifTrue(
                                new BytecodeBlock()
                                    .append(
                                        blocks.setElement(
                                            index,
                                            inputBlock
                                                .cast(LazyBlock.class)
                                                .invoke("getBlock", Block.class)))
                                    .append(createNewPage.set(constantTrue())))
                            .ifFalse(blocks.setElement(index, inputBlock))));

    body.append(forLoop);
    body.append(
        new IfStatement()
            .condition(createNewPage)
            .ifTrue(page.set(newInstance(Page.class, positionCount, blocks))));

    body.append(page.ret());
  }
  private static MethodDefinition generateProjectDictionaryMethod(
      ClassDefinition classDefinition,
      String methodName,
      RowExpression projection,
      MethodDefinition project,
      MethodDefinition projectColumnar) {
    Parameter session = arg("session", ConnectorSession.class);
    Parameter page = arg("page", Page.class);
    Parameter selectedPositions = arg("selectedPositions", int[].class);
    Parameter pageBuilder = arg("pageBuilder", PageBuilder.class);
    Parameter projectionIndex = arg("projectionIndex", int.class);

    List<Parameter> params =
        ImmutableList.<Parameter>builder()
            .add(session)
            .add(page)
            .add(selectedPositions)
            .add(pageBuilder)
            .add(projectionIndex)
            .build();

    MethodDefinition method =
        classDefinition.declareMethod(a(PRIVATE), methodName, type(Block.class), params);
    BytecodeBlock body = method.getBody();
    Scope scope = method.getScope();
    Variable thisVariable = method.getThis();

    List<Integer> inputChannels = getInputChannels(projection);

    if (inputChannels.size() != 1) {
      body.append(thisVariable.invoke(projectColumnar, params).ret());
      return method;
    }

    Variable inputBlock =
        scope.declareVariable(
            "inputBlock",
            body,
            page.invoke(
                "getBlock", Block.class, constantInt(Iterables.getOnlyElement(inputChannels))));
    IfStatement ifStatement =
        new IfStatement()
            .condition(inputBlock.instanceOf(DictionaryBlock.class))
            .ifFalse(thisVariable.invoke(projectColumnar, params).ret());
    body.append(ifStatement);

    Variable blockBuilder =
        scope.declareVariable(
            "blockBuilder",
            body,
            pageBuilder.invoke("getBlockBuilder", BlockBuilder.class, projectionIndex));
    Variable cardinality = scope.declareVariable("cardinality", body, selectedPositions.length());

    Variable dictionary = scope.declareVariable(Block.class, "dictionary");
    Variable ids = scope.declareVariable(Slice.class, "ids");
    Variable dictionaryCount = scope.declareVariable(int.class, "dictionaryCount");

    Variable outputDictionary = scope.declareVariable(Block.class, "outputDictionary");
    Variable outputIds = scope.declareVariable(int[].class, "outputIds");

    BytecodeExpression inputDictionaries =
        thisVariable.getField("inputDictionaries", Block[].class);
    BytecodeExpression outputDictionaries =
        thisVariable.getField("outputDictionaries", Block[].class);

    Variable position = scope.declareVariable("position", body, constantInt(0));

    body.comment("Extract dictionary and ids")
        .append(
            dictionary.set(
                inputBlock.cast(DictionaryBlock.class).invoke("getDictionary", Block.class)))
        .append(ids.set(inputBlock.cast(DictionaryBlock.class).invoke("getIds", Slice.class)))
        .append(dictionaryCount.set(dictionary.invoke("getPositionCount", int.class)));

    BytecodeBlock projectDictionary =
        new BytecodeBlock()
            .comment("Project dictionary")
            .append(
                new ForLoop()
                    .initialize(position.set(constantInt(0)))
                    .condition(lessThan(position, dictionaryCount))
                    .update(position.increment())
                    .body(
                        invokeProject(
                            thisVariable,
                            session,
                            ImmutableList.of(dictionary),
                            position,
                            pageBuilder,
                            projectionIndex,
                            project)))
            .append(outputDictionary.set(blockBuilder.invoke("build", Block.class)))
            .append(inputDictionaries.setElement(projectionIndex, dictionary))
            .append(outputDictionaries.setElement(projectionIndex, outputDictionary));

    body.comment("Use processed dictionary, if available, else project it")
        .append(
            new IfStatement()
                .condition(equal(inputDictionaries.getElement(projectionIndex), dictionary))
                .ifTrue(outputDictionary.set(outputDictionaries.getElement(projectionIndex)))
                .ifFalse(projectDictionary));

    body.comment("Filter ids")
        .append(outputIds.set(newArray(type(int[].class), cardinality)))
        .append(
            new ForLoop()
                .initialize(position.set(constantInt(0)))
                .condition(lessThan(position, cardinality))
                .update(position.increment())
                .body(
                    outputIds.setElement(
                        position,
                        ids.invoke(
                            "getInt",
                            int.class,
                            multiply(
                                selectedPositions.getElement(position),
                                constantInt(SIZE_OF_INT))))));

    body.append(
        newInstance(
                DictionaryBlock.class,
                cardinality,
                outputDictionary,
                invokeStatic(Slices.class, "wrappedIntArray", Slice.class, outputIds))
            .cast(Block.class)
            .ret());
    return method;
  }
  private static MethodDefinition generateProjectColumnarMethod(
      ClassDefinition classDefinition,
      CallSiteBinder callSiteBinder,
      String methodName,
      RowExpression projection,
      MethodDefinition projectionMethod) {
    Parameter session = arg("session", ConnectorSession.class);
    Parameter page = arg("page", Page.class);
    Parameter selectedPositions = arg("selectedPositions", int[].class);
    Parameter pageBuilder = arg("pageBuilder", PageBuilder.class);
    Parameter projectionIndex = arg("projectionIndex", int.class);

    List<Parameter> params =
        ImmutableList.<Parameter>builder()
            .add(session)
            .add(page)
            .add(selectedPositions)
            .add(pageBuilder)
            .add(projectionIndex)
            .build();
    MethodDefinition method =
        classDefinition.declareMethod(a(PRIVATE), methodName, type(Block.class), params);

    BytecodeBlock body = method.getBody();
    Scope scope = method.getScope();
    Variable thisVariable = method.getThis();

    ImmutableList.Builder<Variable> builder = ImmutableList.<Variable>builder();
    for (int channel : getInputChannels(projection)) {
      Variable blockVariable =
          scope.declareVariable(
              "block_" + channel, body, page.invoke("getBlock", Block.class, constantInt(channel)));
      builder.add(blockVariable);
    }
    List<Variable> inputs = builder.build();

    Variable positionCount =
        scope.declareVariable("positionCount", body, page.invoke("getPositionCount", int.class));
    Variable position = scope.declareVariable("position", body, constantInt(0));
    Variable cardinality = scope.declareVariable("cardinality", body, selectedPositions.length());
    Variable outputBlock = scope.declareVariable(Block.class, "outputBlock");
    Variable blockBuilder =
        scope.declareVariable(
            "blockBuilder",
            body,
            pageBuilder.invoke("getBlockBuilder", BlockBuilder.class, projectionIndex));
    Variable type =
        scope.declareVariable(
            "type", body, pageBuilder.invoke("getType", Type.class, projectionIndex));

    BytecodeBlock projectBlock =
        new BytecodeBlock()
            .append(
                new ForLoop()
                    .initialize(position.set(constantInt(0)))
                    .condition(lessThan(position, cardinality))
                    .update(position.increment())
                    .body(
                        invokeProject(
                            thisVariable,
                            session,
                            inputs,
                            selectedPositions.getElement(position),
                            pageBuilder,
                            projectionIndex,
                            projectionMethod)))
            .append(outputBlock.set(blockBuilder.invoke("build", Block.class)));

    if (isIdentityExpression(projection)) {
      // if nothing is filtered out, copy the entire block, else project it
      body.append(
          new IfStatement()
              .condition(equal(cardinality, positionCount))
              .ifTrue(outputBlock.set(inputs.get(0)))
              .ifFalse(projectBlock));
    } else if (isConstantExpression(projection)) {
      // if projection is a constant, create RLE block of constant expression with cardinality
      // positions
      ConstantExpression constantExpression = (ConstantExpression) projection;
      verify(getInputChannels(projection).isEmpty());
      BytecodeExpression value =
          loadConstant(callSiteBinder, constantExpression.getValue(), Object.class);
      body.append(
          outputBlock.set(
              invokeStatic(
                  RunLengthEncodedBlock.class, "create", Block.class, type, value, cardinality)));
    } else {
      body.append(projectBlock);
    }
    body.append(outputBlock.ret());
    return method;
  }