private static List<Integer> getHashChannels(RowPagesBuilder probe, RowPagesBuilder build) {
   ImmutableList.Builder<Integer> hashChannels = ImmutableList.builder();
   if (probe.getHashChannel().isPresent()) {
     hashChannels.add(probe.getHashChannel().get());
   }
   if (build.getHashChannel().isPresent()) {
     hashChannels.add(probe.getTypes().size() + build.getHashChannel().get());
   }
   return hashChannels.build();
 }
  @Test(dataProvider = "hashEnabledValues")
  public void testDistinctLimit(boolean hashEnabled) throws Exception {
    RowPagesBuilder rowPagesBuilder = rowPagesBuilder(hashEnabled, Ints.asList(0), BIGINT);
    List<Page> input = rowPagesBuilder.addSequencePage(3, 1).addSequencePage(5, 2).build();

    OperatorFactory operatorFactory =
        new DistinctLimitOperator.DistinctLimitOperatorFactory(
            0,
            new PlanNodeId("test"),
            ImmutableList.of(BIGINT),
            Ints.asList(0),
            5,
            rowPagesBuilder.getHashChannel());

    MaterializedResult expected =
        resultBuilder(driverContext.getSession(), BIGINT)
            .row(1L)
            .row(2L)
            .row(3L)
            .row(4L)
            .row(5L)
            .build();

    assertOperatorEquals(operatorFactory, driverContext, input, expected);
  }
  @Test(dataProvider = "hashEnabledValues")
  public void testProbeOuterJoin(
      boolean parallelBuild, boolean probeHashEnabled, boolean buildHashEnabled) throws Exception {
    TaskContext taskContext = createTaskContext();

    // build
    List<Type> buildTypes = ImmutableList.<Type>of(VARCHAR, BIGINT, BIGINT);
    RowPagesBuilder buildPages =
        rowPagesBuilder(buildHashEnabled, Ints.asList(0), ImmutableList.of(VARCHAR, BIGINT, BIGINT))
            .addSequencePage(10, 20, 30, 40);
    LookupSourceSupplier lookupSourceSupplier =
        buildHash(parallelBuild, taskContext, Ints.asList(0), buildPages);

    // probe
    List<Type> probeTypes = ImmutableList.<Type>of(VARCHAR, BIGINT, BIGINT);
    RowPagesBuilder probePages = rowPagesBuilder(probeHashEnabled, Ints.asList(0), probeTypes);
    List<Page> probeInput = probePages.addSequencePage(15, 20, 1020, 2020).build();
    OperatorFactory joinOperatorFactory =
        LookupJoinOperators.probeOuterJoin(
            0,
            new PlanNodeId("test"),
            lookupSourceSupplier,
            probePages.getTypes(),
            Ints.asList(0),
            probePages.getHashChannel());
    Operator joinOperator =
        joinOperatorFactory.createOperator(
            taskContext.addPipelineContext(true, true).addDriverContext());

    // expected
    // expected
    MaterializedResult expected =
        MaterializedResult.resultBuilder(taskContext.getSession(), concat(probeTypes, buildTypes))
            .row("20", 1020, 2020, "20", 30, 40)
            .row("21", 1021, 2021, "21", 31, 41)
            .row("22", 1022, 2022, "22", 32, 42)
            .row("23", 1023, 2023, "23", 33, 43)
            .row("24", 1024, 2024, "24", 34, 44)
            .row("25", 1025, 2025, "25", 35, 45)
            .row("26", 1026, 2026, "26", 36, 46)
            .row("27", 1027, 2027, "27", 37, 47)
            .row("28", 1028, 2028, "28", 38, 48)
            .row("29", 1029, 2029, "29", 39, 49)
            .row("30", 1030, 2030, null, null, null)
            .row("31", 1031, 2031, null, null, null)
            .row("32", 1032, 2032, null, null, null)
            .row("33", 1033, 2033, null, null, null)
            .row("34", 1034, 2034, null, null, null)
            .build();

    assertOperatorEquals(
        joinOperator, probeInput, expected, true, getHashChannels(probePages, buildPages));
  }
  @Test(dataProvider = "hashEnabledValues")
  public void testOuterJoinWithNullOnBothSides(
      boolean parallelBuild, boolean probeHashEnabled, boolean buildHashEnabled) throws Exception {
    TaskContext taskContext = createTaskContext();

    // build
    RowPagesBuilder buildPages =
        rowPagesBuilder(buildHashEnabled, Ints.asList(0), ImmutableList.of(VARCHAR))
            .row("a")
            .row((String) null)
            .row((String) null)
            .row("a")
            .row("b");
    LookupSourceSupplier lookupSourceSupplier =
        buildHash(parallelBuild, taskContext, Ints.asList(0), buildPages);

    // probe
    List<Type> probeTypes = ImmutableList.<Type>of(VARCHAR);
    RowPagesBuilder probePages = rowPagesBuilder(probeHashEnabled, Ints.asList(0), probeTypes);
    List<Page> probeInput = probePages.row("a").row("b").row((String) null).row("c").build();
    OperatorFactory joinOperatorFactory =
        LookupJoinOperators.probeOuterJoin(
            0,
            new PlanNodeId("test"),
            lookupSourceSupplier,
            probePages.getTypes(),
            Ints.asList(0),
            probePages.getHashChannel());
    Operator joinOperator =
        joinOperatorFactory.createOperator(
            taskContext.addPipelineContext(true, true).addDriverContext());

    // expected
    MaterializedResult expected =
        MaterializedResult.resultBuilder(
                taskContext.getSession(), concat(probeTypes, buildPages.getTypes()))
            .row("a", "a")
            .row("a", "a")
            .row("b", "b")
            .row(null, null)
            .row("c", null)
            .build();

    assertOperatorEquals(
        joinOperator, probeInput, expected, true, getHashChannels(probePages, buildPages));
  }
  private static LookupSourceSupplier buildHash(
      boolean parallelBuild,
      TaskContext taskContext,
      List<Integer> hashChannels,
      RowPagesBuilder buildPages) {
    if (parallelBuild) {
      ParallelHashBuilder parallelHashBuilder =
          new ParallelHashBuilder(
              buildPages.getTypes(),
              hashChannels,
              buildPages.getHashChannel(),
              100,
              PARTITION_COUNT);

      // collect input data
      DriverContext collectDriverContext =
          taskContext.addPipelineContext(true, true).addDriverContext();
      ValuesOperatorFactory valuesOperatorFactory =
          new ValuesOperatorFactory(
              0, new PlanNodeId("test"), buildPages.getTypes(), buildPages.build());
      OperatorFactory collectOperatorFactory =
          parallelHashBuilder.getCollectOperatorFactory(1, new PlanNodeId("test"));
      Driver driver =
          new Driver(
              collectDriverContext,
              valuesOperatorFactory.createOperator(collectDriverContext),
              collectOperatorFactory.createOperator(collectDriverContext));

      while (!driver.isFinished()) {
        driver.process();
      }

      // build hash tables
      PipelineContext buildPipeline = taskContext.addPipelineContext(true, true);
      OperatorFactory buildOperatorFactory =
          parallelHashBuilder.getBuildOperatorFactory(new PlanNodeId("test"));
      for (int i = 0; i < PARTITION_COUNT; i++) {
        DriverContext buildDriverContext = buildPipeline.addDriverContext();
        Driver buildDriver =
            new Driver(buildDriverContext, buildOperatorFactory.createOperator(buildDriverContext));

        while (!buildDriver.isFinished()) {
          buildDriver.process();
        }
      }

      return parallelHashBuilder.getLookupSourceSupplier();
    } else {
      DriverContext driverContext = taskContext.addPipelineContext(true, true).addDriverContext();

      ValuesOperatorFactory valuesOperatorFactory =
          new ValuesOperatorFactory(
              0, new PlanNodeId("test"), buildPages.getTypes(), buildPages.build());
      HashBuilderOperatorFactory hashBuilderOperatorFactory =
          new HashBuilderOperatorFactory(
              1,
              new PlanNodeId("test"),
              buildPages.getTypes(),
              hashChannels,
              buildPages.getHashChannel(),
              100);

      Driver driver =
          new Driver(
              driverContext,
              valuesOperatorFactory.createOperator(driverContext),
              hashBuilderOperatorFactory.createOperator(driverContext));

      while (!driver.isFinished()) {
        driver.process();
      }
      return hashBuilderOperatorFactory.getLookupSourceSupplier();
    }
  }