private TestCaseResult executeInserts(
      Data data,
      TestCaseResult normalTestResult,
      Map<Integer, TestCaseResult> failedMutants,
      TestCase testCase) {
    for (Table table : schema.getTablesInOrder()) {
      if (data.getTables().contains(table)) {
        List<Integer> applicableMutants = changedTableMap.get(table.getIdentifier().get());
        applicableMutants =
            applicableMutants == null ? new ArrayList<Integer>() : applicableMutants;
        for (Row row : data.getRows(table)) {
          String insert = sqlWriter.writeInsertStatement(row);
          // Only insert if we haven't failed yet
          if (normalTestResult == null) {
            Integer normalResult = databaseInteractor.executeUpdate(insert);
            if (normalResult != 1) {
              normalTestResult =
                  new TestCaseResult(
                      new InsertStatementException("Failed, result was: " + normalResult, insert));
            }
          }

          // Setup for parallel execution
          executor = Executors.newFixedThreadPool(4);
          for (Integer mutantId : applicableMutants) {
            // Only insert if we haven't failed yet
            if (!failedMutants.containsKey(mutantId)) {
              MutantInsertsTask task =
                  new MutantInsertsTask(insert, mutantId, failedMutants, testCase);
              executor.submit(task);
            }
          }
          executor.shutdown();
          try {
            executor.awaitTermination(1, TimeUnit.DAYS);
          } catch (InterruptedException ex) {
            throw new RuntimeException(ex);
          }

          // If a mutant isn't applicable, then it should 'inherit' the normal result
          for (int i = 0; i < mutants.size(); i++) {
            if (!applicableMutants.contains(i) && normalTestResult != null) {
              resultMap.get(i).add(testCase, normalTestResult);
              failedMutants.put(i, normalTestResult);
            }
          }
        }
      }
    }
    return normalTestResult;
  }
  @Override
  public AnalysisResult analyse(TestSuiteResult originalResults) {
    // Get normal results
    AnalysisResult result = new AnalysisResult();

    // Build map of changed tables
    this.changedTableMap = new HashMap<>();
    executor = Executors.newFixedThreadPool(4);
    for (int id = 0; id < mutants.size(); id++) {
      Mutant<Schema> mutant = mutants.get(id);
      executor.submit(new ChangedTableTask(changedTableMap, id, mutant));
    }
    executor.shutdown();
    try {
      executor.awaitTermination(1, TimeUnit.DAYS);
    } catch (InterruptedException ex) {
      throw new RuntimeException(ex);
    }

    // Build the meta-mutant schema and SQL statements
    Schema metamutant = MutationAnalysisUtils.mergeMutantsParallel(schema, mutants);
    createStmts = sqlWriter.writeCreateTableStatements(metamutant);
    dropStmts = sqlWriter.writeDropTableStatements(metamutant, true);
    deleteStmts = sqlWriter.writeDeleteFromTableStatements(metamutant);

    // Build map of results
    this.resultMap = new HashMap<>();
    for (int i = 0; i < mutants.size(); i++) {
      resultMap.put(i, new TestSuiteResult());
    }

    // Execute test suite
    executeDropStmts(databaseInteractor);
    executeCreateStmts(databaseInteractor);
    for (TestCase testCase : testSuite.getTestCases()) {
      executeDeleteStmts(databaseInteractor);
      TestCaseResult normalTestResult = null;
      Map<Integer, TestCaseResult> failedMutants = new HashMap<>();

      Data data = testCase.getState();
      normalTestResult = executeInserts(data, normalTestResult, failedMutants, testCase);
      data = testCase.getData();
      normalTestResult = executeInserts(data, normalTestResult, failedMutants, testCase);
      if (normalTestResult == null) {
        for (int i = 0; i < mutants.size(); i++) {
          if (!failedMutants.containsKey(i)) {
            resultMap.get(i).add(testCase, TestCaseResult.SuccessfulTestCaseResult);
          }
        }
      }
    }

    // Build the TestSuiteResult objects
    for (int i = 0; i < mutants.size(); i++) {
      TestSuiteResult mutantResult = resultMap.get(i);
      if (!originalResults.equals(mutantResult)) {
        result.addKilled(mutants.get(i));
      } else {
        result.addLive(mutants.get(i));
      }
    }

    executeDropStmts(databaseInteractor);
    return result;
  }
  @Override
  public void task() {
    // Setup
    if (inputfolder == null) {
      inputfolder =
          locationsConfiguration.getResultsDir()
              + File.separator
              + "generatedresults"
              + File.separator;
    }
    if (outputfolder == null) {
      outputfolder = locationsConfiguration.getResultsDir() + File.separator;
    }

    // Start results file
    CSVResult result = new CSVResult();
    result.addValue("technique", this.getClass().getSimpleName());
    result.addValue("dbms", databaseConfiguration.getDbms());
    result.addValue("casestudy", casestudy);
    result.addValue("trial", trial);

    // Instantiate the DBMS and related objects
    DBMS dbms = DBMSFactory.instantiate(databaseConfiguration.getDbms());
    SQLWriter sqlWriter = dbms.getSQLWriter();
    DatabaseInteractor databaseInteractor =
        dbms.getDatabaseInteractor(casestudy, databaseConfiguration, locationsConfiguration);

    if (databaseInteractor.getTableCount() != 0) {
      LOGGER.log(
          Level.SEVERE,
          "Potential dirty database detected: technique={0}, casestudy={1}, trial={2}",
          new Object[] {this.getClass().getSimpleName(), casestudy, trial});
    }

    // Get the required schema class
    Schema schema;
    try {
      schema = (Schema) Class.forName(casestudy).newInstance();
    } catch (ClassNotFoundException | IllegalAccessException | InstantiationException ex) {
      throw new RuntimeException(ex);
    }

    // Load the SQLExecutionReport for the non-mutated schema
    String reportPath = inputfolder + casestudy + ".xml";
    SQLExecutionReport originalReport = XMLSerialiser.load(reportPath);

    // Start mutation timing
    ExperimentTimer timer = new ExperimentTimer();
    timer.start(ExperimentTimer.TimingPoint.TOTAL_TIME);

    // Get the mutation pipeline and generate mutants
    timer.start(ExperimentTimer.TimingPoint.MUTATION_TIME);
    MutationPipeline<Schema> pipeline;
    try {
      pipeline =
          MutationPipelineFactory.<Schema>instantiate(
              mutationPipeline, schema, databaseConfiguration.getDbms());
    } catch (ClassNotFoundException
        | InstantiationException
        | IllegalAccessException
        | InvocationTargetException
        | NoSuchMethodException ex) {
      throw new RuntimeException(ex);
    }
    List<Mutant<Schema>> mutants = pipeline.mutate();
    timer.stop(ExperimentTimer.TimingPoint.MUTATION_TIME);

    // Schemata step: Rename the mutants
    renameMutants(mutants);

    // Schemata step: Build single drop statement
    timer.start(ExperimentTimer.TimingPoint.DROPS_TIME);
    StringBuilder dropBuilder = new StringBuilder();
    for (Mutant<Schema> mutant : mutants) {
      Schema mutantSchema = mutant.getMutatedArtefact();
      for (String statement : sqlWriter.writeDropTableStatements(mutantSchema, true)) {
        dropBuilder.append(statement);
        dropBuilder.append("; ");
        dropBuilder.append(System.lineSeparator());
      }
    }
    String dropStmt = dropBuilder.toString();
    timer.stop(ExperimentTimer.TimingPoint.DROPS_TIME);

    // Schemata step: Build single create statement
    timer.start(ExperimentTimer.TimingPoint.CREATES_TIME);
    StringBuilder createBuilder = new StringBuilder();
    for (Mutant<Schema> mutant : mutants) {
      Schema mutantSchema = mutant.getMutatedArtefact();
      for (String statement : sqlWriter.writeCreateTableStatements(mutantSchema)) {
        createBuilder.append(statement);
        createBuilder.append("; ");
        createBuilder.append(System.lineSeparator());
      }
    }
    String createStmt = createBuilder.toString();
    timer.stop(ExperimentTimer.TimingPoint.CREATES_TIME);

    // Schemata step: Drop existing tables before iterating mutants
    timer.start(ExperimentTimer.TimingPoint.DROPS_TIME);
    if (dropfirst) {
      databaseInteractor.executeUpdate(dropStmt);
    }
    timer.stop(ExperimentTimer.TimingPoint.DROPS_TIME);

    // Schemata step: Create table before iterating mutants
    timer.start(ExperimentTimer.TimingPoint.CREATES_TIME);
    Integer res = databaseInteractor.executeUpdate(createStmt);
    boolean quasiSchema = false;
    if (res.intValue() == -1) {
      quasiSchema = true;
    }
    timer.stop(ExperimentTimer.TimingPoint.CREATES_TIME);

    // Only do mutation analysis if the schema is valid
    int killed = 0;
    if (!quasiSchema) {
      // Begin mutation analysis
      for (int id = 0; id < mutants.size(); id++) {
        Schema mutant = mutants.get(id).getMutatedArtefact();

        LOGGER.log(Level.INFO, "Mutant {0}", id);

        // Schemata step: Generate insert prefix string
        String schemataPrefix = "INSERT INTO mutant_" + (id + 1) + "_";

        // Insert the test data
        timer.start(ExperimentTimer.TimingPoint.INSERTS_TIME);
        List<SQLInsertRecord> insertStmts = originalReport.getInsertStatements();
        ArrayList<String> groupedStatements = new ArrayList<>();
        for (SQLInsertRecord insertRecord : insertStmts) {

          // Schemata step: Rewrite insert for mutant ID
          String insertStmt =
              insertRecord.getStatement().replaceAll("INSERT INTO ", schemataPrefix);
          if (insertRecord.isSatisfying()) {
            groupedStatements.add(insertStmt);
          } else {
            if (!groupedStatements.isEmpty()) {
              int returnCount = databaseInteractor.executeUpdatesAsTransaction(groupedStatements);
              groupedStatements.clear();
              if (returnCount == 0) {
                killed++;
                break;
              }
            }
            int returnCount = databaseInteractor.executeUpdate(insertStmt);
            if (returnCount != insertRecord.getReturnCode()) {
              killed++;
              break; // Stop once killed
            }
          }
        }
        if (!groupedStatements.isEmpty()) {
          int returnCount = databaseInteractor.executeUpdatesAsTransaction(groupedStatements);
          groupedStatements.clear();
          if (returnCount == 0) {
            killed++;
          }
        }
        timer.stop(ExperimentTimer.TimingPoint.INSERTS_TIME);
      }
    }

    // Schemata step: Drop tables after iterating mutants
    timer.start(ExperimentTimer.TimingPoint.DROPS_TIME);
    databaseInteractor.executeUpdate(dropStmt);
    timer.stop(ExperimentTimer.TimingPoint.DROPS_TIME);

    timer.stopAll();
    timer.finalise();

    result.addValue("scorenumerator", (!quasiSchema) ? killed : mutants.size());
    result.addValue("scoredenominator", mutants.size());
    result.addValue("mutationpipeline", mutationPipeline.replaceAll(",", "|"));
    result.addValue("threads", 1);
    result.addValue("totaltime", timer.getTime(ExperimentTimer.TimingPoint.TOTAL_TIME));
    result.addValue("dropstime", timer.getTime(ExperimentTimer.TimingPoint.DROPS_TIME));
    result.addValue("createstime", timer.getTime(ExperimentTimer.TimingPoint.CREATES_TIME));
    result.addValue("insertstime", timer.getTime(ExperimentTimer.TimingPoint.INSERTS_TIME));
    result.addValue("mutationtime", timer.getTime(ExperimentTimer.TimingPoint.MUTATION_TIME));
    result.addValue("paralleltime", timer.getTime(ExperimentTimer.TimingPoint.PARALLEL_TIME));

    if (resultsToFile) {
      if (resultsToOneFile) {
        new CSVFileWriter(outputfolder + "mutationanalysis.dat").write(result);
      } else {
        new CSVFileWriter(outputfolder + casestudy + ".dat").write(result);
      }
    }
    if (resultsToDatabase) {
      new CSVDatabaseWriter(databaseConfiguration, new ExperimentConfiguration()).write(result);
    }
  }