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 void generateProcessColumnarMethod(
      ClassDefinition classDefinition,
      List<RowExpression> projections,
      List<MethodDefinition> projectColumnarMethods) {
    Parameter session = arg("session", ConnectorSession.class);
    Parameter page = arg("page", Page.class);
    Parameter types = arg("types", List.class);
    MethodDefinition method =
        classDefinition.declareMethod(
            a(PUBLIC), "processColumnar", type(Page.class), session, page, types);

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

    Variable selectedPositions =
        scope.declareVariable(
            "selectedPositions",
            body,
            thisVariable.invoke("filterPage", int[].class, session, page));
    Variable cardinality = scope.declareVariable("cardinality", body, selectedPositions.length());

    body.comment("if no rows selected return null")
        .append(
            new IfStatement()
                .condition(equal(cardinality, constantInt(0)))
                .ifTrue(constantNull(Page.class).ret()));

    if (projections.isEmpty()) {
      // if no projections, return new page with selected rows
      body.append(newInstance(Page.class, cardinality, newArray(type(Block[].class), 0)).ret());
      return;
    }

    Variable pageBuilder =
        scope.declareVariable(
            "pageBuilder", body, newInstance(PageBuilder.class, cardinality, types));
    Variable outputBlocks =
        scope.declareVariable(
            "outputBlocks", body, newArray(type(Block[].class), projections.size()));

    for (int projectionIndex = 0; projectionIndex < projections.size(); projectionIndex++) {
      List<BytecodeExpression> params =
          ImmutableList.<BytecodeExpression>builder()
              .add(session)
              .add(page)
              .add(selectedPositions)
              .add(pageBuilder)
              .add(constantInt(projectionIndex))
              .build();
      body.append(
          outputBlocks.setElement(
              projectionIndex,
              thisVariable.invoke(projectColumnarMethods.get(projectionIndex), params)));
    }

    // create new page from outputBlocks
    body.append(newInstance(Page.class, cardinality, outputBlocks).ret());
  }
  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 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;
  }