presto-cli/src/main/java/io/prestosql/cli/QueryPreprocessor.java/QueryPreprocessor/preprocessQueryInternal
class QueryPreprocessor:    private static String preprocessQueryInternal(Optional<String> catalog, Optional<String> schema, String query, List<String> preprocessorCommand, Duration timeout)
            throws QueryPreprocessorException
    {
        // execute the process in a child thread so we can better handle interruption and timeouts
        AtomicReference<Process> processReference = new AtomicReference<>();

        Future<String> task = executeInNewThread("Query preprocessor", () -> {
            String result;
            int exitCode;
            Future<String> readStderr;
            try {
                ProcessBuilder processBuilder = new ProcessBuilder(preprocessorCommand);
                processBuilder.environment().put(ENV_PRESTO_CATALOG, catalog.orElse(""));
                processBuilder.environment().put(ENV_PRESTO_SCHEMA, schema.orElse(""));

                Process process = processBuilder.start();
                processReference.set(process);

                Future<?> writeOutput = null;
                try {
                    // write query to process standard out
                    writeOutput = executeInNewThread("Query preprocessor output", () -> {
                        try (OutputStream outputStream = process.getOutputStream()) {
                            outputStream.write(query.getBytes(UTF_8));
                        }
                        return null;
                    });

                    // read stderr
                    readStderr = executeInNewThread("Query preprocessor read stderr", () -> {
                        StringBuilder builder = new StringBuilder();
                        try (InputStream inputStream = process.getErrorStream()) {
                            CharStreams.copy(new InputStreamReader(inputStream, UTF_8), builder);
                        }
                        catch (IOException | RuntimeException ignored) {
                        }
                        return builder.toString();
                    });

                    // read response
                    try (InputStream inputStream = process.getInputStream()) {
                        result = CharStreams.toString(new InputStreamReader(inputStream, UTF_8));
                    }

                    // verify output was written successfully
                    try {
                        writeOutput.get();
                    }
                    catch (ExecutionException e) {
                        throw e.getCause();
                    }

                    // wait for process to finish
                    exitCode = process.waitFor();
                }
                finally {
                    process.destroyForcibly();
                    if (writeOutput != null) {
                        writeOutput.cancel(true);
                    }
                }
            }
            catch (QueryPreprocessorException e) {
                throw e;
            }
            catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new QueryPreprocessorException("Interrupted while preprocessing query");
            }
            catch (Throwable e) {
                throw new QueryPreprocessorException("Error preprocessing query: " + e.getMessage(), e);
            }

            // check we got a valid exit code
            if (exitCode != 0) {
                Optional<String> errorMessage = tryGetFutureValue(readStderr, 100, MILLISECONDS)
                        .flatMap(value -> Optional.ofNullable(emptyToNull(value.trim())));

                throw new QueryPreprocessorException("Query preprocessor exited " + exitCode +
                        errorMessage.map(message1 -> "\n===\n" + message1 + "\n===").orElse(""));
            }
            return result;
        });

        try {
            return task.get(timeout.toMillis(), MILLISECONDS);
        }
        catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new QueryPreprocessorException("Interrupted while preprocessing query");
        }
        catch (ExecutionException e) {
            Throwable cause = e.getCause();
            propagateIfPossible(cause, QueryPreprocessorException.class);
            throw new QueryPreprocessorException("Error preprocessing query: " + cause.getMessage(), cause);
        }
        catch (TimeoutException e) {
            throw new QueryPreprocessorException("Timed out waiting for query preprocessor after " + timeout);
        }
        finally {
            Process process = processReference.get();
            if (process != null) {
                process.destroyForcibly();
            }
            task.cancel(true);
        }
    }