@Test
  public void testExecuteConcurrentModification() throws Exception {

    String sql = "SELECT * FROM " + DATA_SERVICE_NAME;
    DataServiceExecutor executor =
        new DataServiceExecutor.Builder(new SQL(sql), dataService, context)
            .prepareExecution(false)
            .sqlTransGenerator(sqlTransGenerator)
            .serviceTrans(serviceTrans)
            .genTrans(genTrans)
            .build();

    final DataServiceExecutor.ExecutionPoint stage = DataServiceExecutor.ExecutionPoint.OPTIMIZE;
    final ListMultimap<DataServiceExecutor.ExecutionPoint, Runnable> listenerMap =
        executor.getListenerMap();
    final Runnable task =
        new Runnable() {
          @Override
          public void run() {
            // Remove itself on run
            assertTrue(listenerMap.remove(stage, this));
          }
        };

    listenerMap.put(stage, task);
    executor.executeQuery();

    // Note the error reported to logs
    verify(genTrans.getLogChannel())
        .logError(anyString(), eq(stage), eq(ImmutableList.of(task)), eq(ImmutableList.of()));
  }
  @Test
  public void testQueryWithParams() throws Exception {
    String sql =
        "SELECT * FROM "
            + DATA_SERVICE_NAME
            + " WHERE PARAMETER('foo') = 'bar' AND PARAMETER('baz') = 'bop'";

    final SQL theSql = new SQL(sql);

    DataServiceExecutor executor =
        new DataServiceExecutor.Builder(theSql, dataService, context)
            .serviceTrans(serviceTrans)
            .sqlTransGenerator(sqlTransGenerator)
            .genTrans(genTrans)
            .parameters(ImmutableMap.of("BUILD_PARAM", "TRUE"))
            .build();

    List<Condition> conditions = theSql.getWhereCondition().getCondition().getChildren();

    assertEquals(2, conditions.size());
    for (Condition condition : conditions) {
      // verifies that each of the parameter conditions have their left and right valuename
      // set to null after executor initialization.  This prevents failure due to non-existent
      // fieldnames being present.
      assertNull(condition.getLeftValuename());
      assertNull(condition.getRightValuename());
    }

    assertThat(
        executor.getParameters(),
        equalTo(
            (Map<String, String>)
                ImmutableMap.of(
                    "baz", "bop",
                    "foo", "bar",
                    "BUILD_PARAM", "TRUE")));

    // Late parameter modification is okay
    executor.getParameters().put("AFTER_BUILD", "TRUE");

    // Parameters should not be set on the trans until execute
    verify(serviceTrans, never()).setParameterValue(anyString(), anyString());

    executor.executeQuery();
    // verify that the parameter values were correctly extracted from the WHERE and applied
    verify(serviceTrans).setParameterValue("foo", "bar");
    verify(serviceTrans).setParameterValue("baz", "bop");
    verify(serviceTrans).setParameterValue("BUILD_PARAM", "TRUE");
    verify(serviceTrans).setParameterValue("AFTER_BUILD", "TRUE");
  }
  @Test
  public void testExecuteQuery() throws Exception {
    SQL sql = new SQL("SELECT * FROM " + DATA_SERVICE_NAME);
    StepInterface serviceStep = serviceTrans.findRunThread(DATA_SERVICE_STEP);
    StepInterface resultStep = genTrans.findRunThread(RESULT_STEP_NAME);

    when(serviceTrans.getTransMeta().listParameters()).thenReturn(new String[0]);

    PushDownOptimizationMeta optimization = mock(PushDownOptimizationMeta.class);
    when(optimization.isEnabled()).thenReturn(true);
    dataService.getPushDownOptimizationMeta().add(optimization);

    IMetaStore metastore = mock(IMetaStore.class);
    DataServiceExecutor executor =
        new DataServiceExecutor.Builder(sql, dataService, context)
            .serviceTrans(serviceTrans)
            .sqlTransGenerator(sqlTransGenerator)
            .genTrans(genTrans)
            .metastore(metastore)
            .build();

    ArgumentCaptor<String> objectIds = ArgumentCaptor.forClass(String.class);
    verify(serviceTrans).setContainerObjectId(objectIds.capture());
    when(serviceTrans.getContainerObjectId()).thenReturn(objectIds.getValue());
    verify(genTrans).setContainerObjectId(objectIds.capture());
    when(genTrans.getContainerObjectId()).thenReturn(objectIds.getValue());
    verify(serviceTrans).setMetaStore(metastore);
    verify(genTrans).setMetaStore(metastore);

    RowProducer sqlTransRowProducer = mock(RowProducer.class);
    when(genTrans.addRowProducer(INJECTOR_STEP_NAME, 0)).thenReturn(sqlTransRowProducer);

    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

    // Start Execution
    executor.executeQuery(new DataOutputStream(outputStream));

    // Check header was written
    assertThat(outputStream.size(), greaterThan(0));
    outputStream.reset();

    InOrder genTransStartup = inOrder(genTrans, resultStep);
    InOrder serviceTransStartup = inOrder(optimization, serviceTrans, serviceStep);
    ArgumentCaptor<RowListener> listenerArgumentCaptor = ArgumentCaptor.forClass(RowListener.class);
    ArgumentCaptor<StepListener> resultStepListener = ArgumentCaptor.forClass(StepListener.class);
    ArgumentCaptor<TransListener> transListenerCaptor =
        ArgumentCaptor.forClass(TransListener.class);

    genTransStartup.verify(genTrans).addTransListener(transListenerCaptor.capture());
    genTransStartup.verify(genTrans).addRowProducer(INJECTOR_STEP_NAME, 0);
    genTransStartup.verify(resultStep).addStepListener(resultStepListener.capture());
    genTransStartup.verify(resultStep).addRowListener(listenerArgumentCaptor.capture());
    RowListener clientRowListener = listenerArgumentCaptor.getValue();
    genTransStartup.verify(genTrans).startThreads();

    serviceTransStartup.verify(optimization).activate(executor);
    serviceTransStartup.verify(serviceStep).addRowListener(listenerArgumentCaptor.capture());
    serviceTransStartup.verify(serviceTrans).startThreads();

    // Verify linkage
    RowListener serviceRowListener = listenerArgumentCaptor.getValue();
    assertNotNull(serviceRowListener);

    // Push row from service to sql Trans
    RowMetaInterface rowMeta = genTrans.getTransMeta().getStepFields(RESULT_STEP_NAME);
    Object[] data;
    for (int i = 0; i < 50; i++) {
      data = new Object[] {i};

      Object[] dataClone = {i};
      when(rowMeta.cloneRow(data)).thenReturn(dataClone);
      serviceRowListener.rowWrittenEvent(rowMeta, data);
      verify(sqlTransRowProducer)
          .putRowWait(
              same(rowMeta),
              and(eq(dataClone), not(same(data))),
              any(Long.class),
              any(TimeUnit.class));
      verify(rowMeta).cloneRow(data);
    }

    doReturn(true).when(serviceTrans).isRunning();
    resultStepListener.getValue().stepFinished(genTrans, resultStep.getStepMeta(), resultStep);
    verify(serviceTrans).stopAll();

    // Verify Service Trans finished
    ArgumentCaptor<StepListener> serviceStepListener = ArgumentCaptor.forClass(StepListener.class);
    verify(serviceStep).addStepListener(serviceStepListener.capture());
    serviceStepListener
        .getValue()
        .stepFinished(serviceTrans, serviceStep.getStepMeta(), serviceStep);
    verify(sqlTransRowProducer).finished();

    // Push row from service to sql Trans
    for (int i = 0; i < 50; i++) {
      Object[] row = {i};
      clientRowListener.rowWrittenEvent(rowMeta, row);
    }
    transListenerCaptor.getValue().transFinished(genTrans);

    InOrder writeRows = inOrder(rowMeta);
    ArgumentCaptor<DataOutputStream> streamCaptor = ArgumentCaptor.forClass(DataOutputStream.class);
    writeRows.verify(rowMeta).writeMeta(streamCaptor.capture());
    DataOutputStream dataOutputStream = streamCaptor.getValue();
    writeRows
        .verify(rowMeta, times(50))
        .writeData(same(dataOutputStream), argThat(arrayWithSize(1)));
    writeRows.verifyNoMoreInteractions();

    executor.waitUntilFinished();
    verify(serviceTrans).waitUntilFinished();
    verify(genTrans).waitUntilFinished();
  }