diff --git a/core/src/main/java/org/apache/druid/query/QueryException.java b/core/src/main/java/org/apache/druid/query/QueryException.java
index b8db5bd48d7..108e9f7226e 100644
--- a/core/src/main/java/org/apache/druid/query/QueryException.java
+++ b/core/src/main/java/org/apache/druid/query/QueryException.java
@@ -21,6 +21,7 @@ package org.apache.druid.query;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.annotations.VisibleForTesting;
import javax.annotation.Nullable;
import java.net.InetAddress;
@@ -44,8 +45,9 @@ public class QueryException extends RuntimeException
this.host = host;
}
+ @VisibleForTesting
@JsonCreator
- protected QueryException(
+ public QueryException(
@JsonProperty("error") @Nullable String errorCode,
@JsonProperty("errorMessage") String errorMessage,
@JsonProperty("errorClass") @Nullable String errorClass,
diff --git a/docs/querying/querying.md b/docs/querying/querying.md
index 15bfc045f77..53577fb4cbe 100644
--- a/docs/querying/querying.md
+++ b/docs/querying/querying.md
@@ -106,6 +106,12 @@ curl -X DELETE "http://host:port/druid/v2/abc123"
## Query errors
+### Authentication and authorization failures
+
+For [secured](../design/auth.md) Druid clusters, query requests respond with an HTTP 401 response code in case of an authentication failure. For authorization failures, an HTTP 403 response code is returned.
+
+### Query execution failures
+
If a query fails, Druid returns a response with an HTTP response code and a JSON object with the following structure:
```json
@@ -116,13 +122,6 @@ If a query fails, Druid returns a response with an HTTP response code and a JSON
"host" : "druid1.example.com:8083"
}
```
-The HTTP response code returned depends on the type of query failure. For timed out queries, an HTTP 504 response code is returned.
-
-For [secured](../design/auth.md) Druid clusters, query requests respond with an HTTP 401 response code in case of an authentication failure. For authorization failures, an HTTP 403 response code is returned.
-
-If a query request fails due to being limited by the [query scheduler laning configuration](../configuration/index.md#broker), an HTTP 429 response with the same JSON object schema as an error response, but with `errorMessage` of the form: "Total query capacity exceeded" or "Query capacity exceeded for lane 'low'".
-
-For every other type of query failures, an HTTP 500 response code is returned.
The fields in the response are:
@@ -133,15 +132,17 @@ The fields in the response are:
|errorClass|The class of the exception that caused this error. May be null.|
|host|The host on which this error occurred. May be null.|
-Possible codes for the *error* field include:
+Possible Druid error codes for the `error` field include:
-|code|description|
-|----|-----------|
-|`Query timeout`|The query timed out.|
-|`Query interrupted`|The query was interrupted, possibly due to JVM shutdown.|
-|`Query cancelled`|The query was cancelled through the query cancellation API.|
-|`Resource limit exceeded`|The query exceeded a configured resource limit (e.g. groupBy maxResults).|
-|`Unauthorized request.`|The query was denied due to security policy. Either the user was not recognized, or the user was recognized but does not have access to the requested resource.|
-|`Unsupported operation`|The query attempted to perform an unsupported operation. This may occur when using undocumented features or when using an incompletely implemented extension.|
-|`Truncated response context`|An intermediate response context for the query exceeded the built-in limit of 7KB.
The response context is an internal data structure that Druid servers use to share out-of-band information when sending query results to each other. It is serialized in an HTTP header with a maximum length of 7KB. This error occurs when an intermediate response context sent from a data server (like a Historical) to the Broker exceeds this limit.
The response context is used for a variety of purposes, but the one most likely to generate a large context is sharing details about segments that move during a query. That means this error can potentially indicate that a very large number of segments moved in between the time a Broker issued a query and the time it was processed on Historicals. This should rarely, if ever, occur during normal operation.|
-|`Unknown exception`|Some other exception occurred. Check errorMessage and errorClass for details, although keep in mind that the contents of those fields are free-form and may change from release to release.|
+|Error code|HTTP response code|description|
+|----|-----------|-----------|
+|`SQL parse failed`|400|Only for SQL queries. The SQL query failed to parse.|
+|`Plan validation failed`|400|Only for SQL queries. The SQL query failed to validate.|
+|`Resource limit exceeded`|400|The query exceeded a configured resource limit (e.g. groupBy maxResults).|
+|`Query capacity exceeded`|429|The query failed to execute because of the lack of resources available at the time when the query was submitted. The resources could be any runtime resources such as [query scheduler lane capacity](../configuration/index.md#query-prioritization-and-laning), merge buffers, and so on. The error message should have more details about the failure.|
+|`Unsupported operation`|501|The query attempted to perform an unsupported operation. This may occur when using undocumented features or when using an incompletely implemented extension.|
+|`Query timeout`|504|The query timed out.|
+|`Query interrupted`|500|The query was interrupted, possibly due to JVM shutdown.|
+|`Query cancelled`|500|The query was cancelled through the query cancellation API.|
+|`Truncated response context`|500|An intermediate response context for the query exceeded the built-in limit of 7KB.
The response context is an internal data structure that Druid servers use to share out-of-band information when sending query results to each other. It is serialized in an HTTP header with a maximum length of 7KB. This error occurs when an intermediate response context sent from a data server (like a Historical) to the Broker exceeds this limit.
The response context is used for a variety of purposes, but the one most likely to generate a large context is sharing details about segments that move during a query. That means this error can potentially indicate that a very large number of segments moved in between the time a Broker issued a query and the time it was processed on Historicals. This should rarely, if ever, occur during normal operation.|
+|`Unknown exception`|500|Some other exception occurred. Check errorMessage and errorClass for details, although keep in mind that the contents of those fields are free-form and may change from release to release.|
\ No newline at end of file
diff --git a/processing/src/main/java/org/apache/druid/query/ResourceLimitExceededException.java b/processing/src/main/java/org/apache/druid/query/ResourceLimitExceededException.java
index 8a3ba000be9..169d774cabf 100644
--- a/processing/src/main/java/org/apache/druid/query/ResourceLimitExceededException.java
+++ b/processing/src/main/java/org/apache/druid/query/ResourceLimitExceededException.java
@@ -35,9 +35,19 @@ public class ResourceLimitExceededException extends BadQueryException
{
public static final String ERROR_CODE = "Resource limit exceeded";
- public ResourceLimitExceededException(String message, Object... arguments)
+ public static ResourceLimitExceededException withMessage(String message, Object... arguments)
{
- this(ERROR_CODE, StringUtils.nonStrictFormat(message, arguments), ResourceLimitExceededException.class.getName());
+ return new ResourceLimitExceededException(StringUtils.nonStrictFormat(message, arguments));
+ }
+
+ public ResourceLimitExceededException(String errorCode, String message, String errorClass, String host)
+ {
+ super(errorCode, message, errorClass, host);
+ }
+
+ public ResourceLimitExceededException(String message)
+ {
+ this(ERROR_CODE, message, ResourceLimitExceededException.class.getName());
}
@JsonCreator
@@ -47,6 +57,6 @@ public class ResourceLimitExceededException extends BadQueryException
@JsonProperty("errorClass") String errorClass
)
{
- super(errorCode, errorMessage, errorClass, resolveHostname());
+ this(errorCode, errorMessage, errorClass, resolveHostname());
}
}
diff --git a/processing/src/main/java/org/apache/druid/query/scan/ScanQueryRunnerFactory.java b/processing/src/main/java/org/apache/druid/query/scan/ScanQueryRunnerFactory.java
index d8c8c7404b4..a5bab7d53e3 100644
--- a/processing/src/main/java/org/apache/druid/query/scan/ScanQueryRunnerFactory.java
+++ b/processing/src/main/java/org/apache/druid/query/scan/ScanQueryRunnerFactory.java
@@ -189,7 +189,7 @@ public class ScanQueryRunnerFactory implements QueryRunnerFactory implements Iterator, Closeable
throw timeoutQuery();
} else {
// TODO: NettyHttpClient should check the actual cause of the failure and set it in the future properly.
- throw new ResourceLimitExceededException(
+ throw ResourceLimitExceededException.withMessage(
"Possibly max scatter-gather bytes limit reached while reading from url[%s].",
url
);
@@ -187,7 +187,10 @@ public class JsonParserIterator implements Iterator, Closeable
);
}
}
- catch (IOException | InterruptedException | ExecutionException | CancellationException e) {
+ catch (ExecutionException | CancellationException e) {
+ throw convertException(e.getCause() == null ? e : e.getCause());
+ }
+ catch (IOException | InterruptedException e) {
throw convertException(e);
}
catch (TimeoutException e) {
@@ -210,7 +213,7 @@ public class JsonParserIterator implements Iterator, Closeable
* based on {@link QueryException#getErrorCode()}. During conversion, {@link QueryException#host} is overridden
* by {@link #host}.
*/
- private QueryException convertException(Exception cause)
+ private QueryException convertException(Throwable cause)
{
LOG.warn(cause, "Query [%s] to host [%s] interrupted", queryId, host);
if (cause instanceof QueryException) {
diff --git a/server/src/main/java/org/apache/druid/server/ClientQuerySegmentWalker.java b/server/src/main/java/org/apache/druid/server/ClientQuerySegmentWalker.java
index be4acb263b3..4719ca89ef6 100644
--- a/server/src/main/java/org/apache/druid/server/ClientQuerySegmentWalker.java
+++ b/server/src/main/java/org/apache/druid/server/ClientQuerySegmentWalker.java
@@ -455,7 +455,10 @@ public class ClientQuerySegmentWalker implements QuerySegmentWalker
final int limitToUse = limit < 0 ? Integer.MAX_VALUE : limit;
if (limitAccumulator.get() >= limitToUse) {
- throw new ResourceLimitExceededException("Cannot issue subquery, maximum[%d] reached", limitToUse);
+ throw ResourceLimitExceededException.withMessage(
+ "Cannot issue subquery, maximum[%d] reached",
+ limitToUse
+ );
}
final RowSignature signature = toolChest.resultArraySignature(query);
@@ -466,7 +469,7 @@ public class ClientQuerySegmentWalker implements QuerySegmentWalker
resultList,
(acc, in) -> {
if (limitAccumulator.getAndIncrement() >= limitToUse) {
- throw new ResourceLimitExceededException(
+ throw ResourceLimitExceededException.withMessage(
"Subquery generated results beyond maximum[%d]",
limitToUse
);
diff --git a/server/src/test/java/org/apache/druid/client/JsonParserIteratorTest.java b/server/src/test/java/org/apache/druid/client/JsonParserIteratorTest.java
new file mode 100644
index 00000000000..e47693ec3c4
--- /dev/null
+++ b/server/src/test/java/org/apache/druid/client/JsonParserIteratorTest.java
@@ -0,0 +1,211 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.druid.client;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.Futures;
+import org.apache.druid.jackson.DefaultObjectMapper;
+import org.apache.druid.query.QueryCapacityExceededException;
+import org.apache.druid.query.QueryException;
+import org.apache.druid.query.QueryInterruptedException;
+import org.apache.druid.query.QueryTimeoutException;
+import org.apache.druid.query.QueryUnsupportedException;
+import org.apache.druid.query.ResourceLimitExceededException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mockito;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+@RunWith(Enclosed.class)
+public class JsonParserIteratorTest
+{
+ private static final JavaType JAVA_TYPE = Mockito.mock(JavaType.class);
+ private static final String URL = "url";
+ private static final String HOST = "host";
+ private static final ObjectMapper OBJECT_MAPPER = new DefaultObjectMapper();
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public static class FutureExceptionTest
+ {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void testConvertFutureTimeoutToQueryTimeoutException()
+ {
+ JsonParserIterator