All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.trino.tests.product.TestSqlCancel Maven / Gradle / Ivy

There is a newer version: 464
Show newest version
/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.trino.tests.product;

import com.google.common.base.Stopwatch;
import com.google.common.io.Closer;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import io.airlift.http.client.HttpClient;
import io.airlift.http.client.HttpClientConfig;
import io.airlift.http.client.HttpStatus;
import io.airlift.http.client.Request;
import io.airlift.http.client.Response;
import io.airlift.http.client.ResponseHandler;
import io.airlift.http.client.jetty.JettyHttpClient;
import io.trino.tempto.AfterMethodWithContext;
import io.trino.tempto.BeforeMethodWithContext;
import io.trino.tempto.ProductTest;
import io.trino.tempto.query.QueryResult;
import org.testng.annotations.Test;

import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeoutException;

import static com.google.common.base.Preconditions.checkState;
import static io.airlift.http.client.HttpUriBuilder.uriBuilderFrom;
import static io.airlift.http.client.Request.Builder.prepareDelete;
import static io.airlift.http.client.ResponseHandlerUtils.propagate;
import static io.trino.tempto.assertions.QueryAssert.assertQueryFailure;
import static io.trino.tests.product.utils.QueryExecutors.onTrino;
import static java.lang.String.format;
import static java.lang.System.nanoTime;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.Executors.newSingleThreadExecutor;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Fail.fail;

public class TestSqlCancel
        extends ProductTest
{
    private ExecutorService executor;
    private QueryCanceller queryCanceller;
    private Closer closer;

    @Inject
    @Named("databases.presto.server_address")
    private String serverAddress;

    @BeforeMethodWithContext
    public void setUp()
    {
        closer = Closer.create();
        executor = newSingleThreadExecutor(); // single thread is enough, it schedules the query to cancel
        closer.register(executor::shutdownNow);
        queryCanceller = closer.register(new QueryCanceller(serverAddress));
    }

    @AfterMethodWithContext
    public void cleanUp()
            throws IOException
    {
        queryCanceller = null; // closed via closer
        executor = null; // closed via closer
        closer.close();
        closer = null;
    }

    @Test(timeOut = 60_000L)
    public void cancelCreateTable()
            throws Exception
    {
        String tableName = "cancel_createtable_" + nanoTime();
        String sql = format("CREATE TABLE %s AS SELECT * FROM tpch.sf1.lineitem", tableName);

        runAndCancelQuery(sql);
        assertQueryFailure(() -> onTrino().executeQuery("SELECT * from " + tableName))
                .hasMessageContaining("Table 'hive.default.%s' does not exist", tableName);
    }

    @Test(timeOut = 60_000L)
    public void cancelInsertInto()
            throws Exception
    {
        String tableName = "cancel_insertinto_" + nanoTime();
        onTrino().executeQuery(format("CREATE TABLE %s (orderkey BIGINT, partkey BIGINT, shipinstruct VARCHAR(25)) ", tableName));
        String sql = format("INSERT INTO %s SELECT orderkey, partkey, shipinstruct FROM tpch.sf1.lineitem", tableName);
        runAndCancelQuery(sql);
        assertThat(onTrino().executeQuery("SELECT * from " + tableName)).hasNoRows();
        onTrino().executeQuery("DROP TABLE " + tableName);
    }

    @Test(timeOut = 60_000L)
    public void cancelSelect()
            throws Exception
    {
        runAndCancelQuery("SELECT * FROM tpch.sf1.lineitem AS cancel_select_" + nanoTime());
    }

    private void runAndCancelQuery(String sql)
            throws Exception
    {
        Future queryExecution = executor.submit(() -> onTrino().executeQuery(sql));

        cancelQuery(sql);

        try {
            queryExecution.get(30, SECONDS);
            fail("Query failure was expected");
        }
        catch (TimeoutException e) {
            queryExecution.cancel(true);
            throw e;
        }
        catch (ExecutionException expected) {
            assertThat(expected.getCause())
                    .hasMessageEndingWith("Query was canceled");
        }
    }

    private void cancelQuery(String sql)
            throws InterruptedException
    {
        Stopwatch stopwatch = Stopwatch.createStarted();
        while (stopwatch.elapsed(SECONDS) < 30) {
            String findQuerySql = "SELECT query_id from system.runtime.queries WHERE query = '%s' and state = 'RUNNING' LIMIT 2";
            QueryResult queryResult = onTrino().executeQuery(format(findQuerySql, sql));
            checkState(queryResult.getRowsCount() < 2, "Query is executed multiple times");
            if (queryResult.getRowsCount() == 1) {
                String queryId = (String) queryResult.getOnlyValue();
                Response response = queryCanceller.cancel(queryId);
                assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT.code());
                return;
            }
            MILLISECONDS.sleep(100L);
        }
        throw new IllegalStateException("Query did not reach running state or maybe it was too quick.");
    }

    private static class QueryCanceller
            implements Closeable
    {
        private final HttpClient httpClient;
        private final URI uri;

        QueryCanceller(String uri)
        {
            this.httpClient = new JettyHttpClient(new HttpClientConfig());
            this.uri = URI.create(requireNonNull(uri, "uri is null"));
        }

        public Response cancel(String queryId)
        {
            requireNonNull(queryId, "queryId is null");
            URI cancelUri = uriBuilderFrom(uri).appendPath("/v1/query").appendPath(queryId).build();
            Request request = prepareDelete()
                    .setHeader("X-Trino-User", "anyUser")
                    .setUri(cancelUri)
                    .build();
            return httpClient.execute(request, new ResponseHandler<>()
            {
                @Override
                public Response handleException(Request request, Exception exception)
                {
                    throw propagate(request, exception);
                }

                @Override
                public Response handle(Request request, Response response)
                {
                    return response;
                }
            });
        }

        @Override
        public void close()
        {
            httpClient.close();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy