clintropolis commented on a change in pull request #11711:
URL: https://github.com/apache/druid/pull/11711#discussion_r712588136



##########
File path: sql/src/main/java/org/apache/druid/sql/http/SqlResource.java
##########
@@ -170,39 +174,50 @@ public Response doPost(
     }
     catch (QueryCapacityExceededException cap) {
       endLifecycle(sqlQueryId, lifecycle, cap, remoteAddr, -1);
-      return buildNonOkResponse(QueryCapacityExceededException.STATUS_CODE, 
cap);
+      return buildNonOkResponse(
+          QueryCapacityExceededException.STATUS_CODE,
+          
serverConfig.getErrorResponseTransformStrategy().transformIfNeeded(cap)
+      );
     }
     catch (QueryUnsupportedException unsupported) {
       endLifecycle(sqlQueryId, lifecycle, unsupported, remoteAddr, -1);
-      return buildNonOkResponse(QueryUnsupportedException.STATUS_CODE, 
unsupported);
+      return buildNonOkResponse(
+          QueryUnsupportedException.STATUS_CODE,
+          
serverConfig.getErrorResponseTransformStrategy().transformIfNeeded(unsupported)
+      );
     }
     catch (QueryTimeoutException timeout) {
       endLifecycle(sqlQueryId, lifecycle, timeout, remoteAddr, -1);
-      return buildNonOkResponse(QueryTimeoutException.STATUS_CODE, timeout);
+      return buildNonOkResponse(
+          QueryTimeoutException.STATUS_CODE,
+          
serverConfig.getErrorResponseTransformStrategy().transformIfNeeded(timeout)
+      );
     }
     catch (SqlPlanningException | ResourceLimitExceededException e) {
       endLifecycle(sqlQueryId, lifecycle, e, remoteAddr, -1);
-      return buildNonOkResponse(BadQueryException.STATUS_CODE, e);
+      return buildNonOkResponse(
+          BadQueryException.STATUS_CODE,
+          serverConfig.getErrorResponseTransformStrategy().transformIfNeeded(e)
+      );
     }
     catch (ForbiddenException e) {
       endLifecycleWithoutEmittingMetrics(sqlQueryId, lifecycle);
-      throw e; // let ForbiddenExceptionMapper handle this
+      throw (ForbiddenException) 
serverConfig.getErrorResponseTransformStrategy().transformIfNeeded(e); // let 
ForbiddenExceptionMapper handle this
     }
     catch (Exception e) {
       log.warn(e, "Failed to handle query: %s", sqlQuery);
       endLifecycle(sqlQueryId, lifecycle, e, remoteAddr, -1);
 
-      final Exception exceptionToReport;
+      QueryInterruptedException exceptionToReport;
 
       if (e instanceof RelOptPlanner.CannotPlanException) {
-        exceptionToReport = new ISE("Cannot build plan for query: %s", 
sqlQuery.getQuery());
+        exceptionToReport = QueryInterruptedException.wrapIfNeeded((new 
ISE("Cannot build plan for query: %s", sqlQuery.getQuery())));

Review comment:
       nit: this isn't new or your change, but I think it is semi confusing 
that there exists a`SqlPlanningException` that isn't being used here. The 
reason for this is because I think that this is a different kind of planning 
exception - one that occurs for some query which we couldn't translate to a 
native Druid query, which we seem to be treating as a server-side error, where 
the other one is currently used for SQL parsing and validation errors and 
considered client side errors since they are things the user can fix.
   
   I wonder if we should have some sort of unsupported query exception to give 
this a bit more prominence, but I think that is a tangential discussion to this 
PRs changed.

##########
File path: 
core/src/main/java/org/apache/druid/common/exception/NoErrorResponseTransformStrategy.java
##########
@@ -0,0 +1,53 @@
+/*
+ * 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.common.exception;
+
+import org.apache.druid.java.util.common.IAE;
+
+import java.util.function.Function;
+
+/**
+ * Error response transform strategy that does nothing and simply return the 
same Exception back without any change
+ */
+public class NoErrorResponseTransformStrategy implements 
ErrorResponseTransformStrategy
+{
+  public static final NoErrorResponseTransformStrategy INSTANCE = new 
NoErrorResponseTransformStrategy();
+
+  @Override
+  public Exception transformIfNeeded(SanitizableException exception)
+  {
+    if (exception instanceof Exception) {
+      return (Exception) exception;
+    } else {
+      return new IAE(
+          "Cannot sanitize error response as given argument[%s] is not an 
Exception",
+          exception.getClass().getName()
+      );
+    }

Review comment:
       why not just `return exception;` and be a no-op like the javadoc says?

##########
File path: sql/src/main/java/org/apache/druid/sql/http/SqlResource.java
##########
@@ -170,39 +174,50 @@ public Response doPost(
     }
     catch (QueryCapacityExceededException cap) {
       endLifecycle(sqlQueryId, lifecycle, cap, remoteAddr, -1);
-      return buildNonOkResponse(QueryCapacityExceededException.STATUS_CODE, 
cap);
+      return buildNonOkResponse(
+          QueryCapacityExceededException.STATUS_CODE,
+          
serverConfig.getErrorResponseTransformStrategy().transformIfNeeded(cap)
+      );
     }
     catch (QueryUnsupportedException unsupported) {
       endLifecycle(sqlQueryId, lifecycle, unsupported, remoteAddr, -1);
-      return buildNonOkResponse(QueryUnsupportedException.STATUS_CODE, 
unsupported);
+      return buildNonOkResponse(
+          QueryUnsupportedException.STATUS_CODE,
+          
serverConfig.getErrorResponseTransformStrategy().transformIfNeeded(unsupported)
+      );
     }
     catch (QueryTimeoutException timeout) {
       endLifecycle(sqlQueryId, lifecycle, timeout, remoteAddr, -1);
-      return buildNonOkResponse(QueryTimeoutException.STATUS_CODE, timeout);
+      return buildNonOkResponse(
+          QueryTimeoutException.STATUS_CODE,
+          
serverConfig.getErrorResponseTransformStrategy().transformIfNeeded(timeout)
+      );
     }
     catch (SqlPlanningException | ResourceLimitExceededException e) {
       endLifecycle(sqlQueryId, lifecycle, e, remoteAddr, -1);
-      return buildNonOkResponse(BadQueryException.STATUS_CODE, e);
+      return buildNonOkResponse(
+          BadQueryException.STATUS_CODE,
+          serverConfig.getErrorResponseTransformStrategy().transformIfNeeded(e)
+      );
     }
     catch (ForbiddenException e) {
       endLifecycleWithoutEmittingMetrics(sqlQueryId, lifecycle);
-      throw e; // let ForbiddenExceptionMapper handle this
+      throw (ForbiddenException) 
serverConfig.getErrorResponseTransformStrategy().transformIfNeeded(e); // let 
ForbiddenExceptionMapper handle this

Review comment:
       at some point in the future, assuming we eventually make this 
functionality apply to all API response errors, it probably makes sense to push 
this transformation functionality into the error handlers so that we everything 
that lets exceptions fall through doesn't need to do this itself. It is 
probably ok to leave here for now since this is the only thing doing it

##########
File path: sql/src/main/java/org/apache/druid/sql/http/SqlResource.java
##########
@@ -170,39 +174,50 @@ public Response doPost(
     }
     catch (QueryCapacityExceededException cap) {
       endLifecycle(sqlQueryId, lifecycle, cap, remoteAddr, -1);
-      return buildNonOkResponse(QueryCapacityExceededException.STATUS_CODE, 
cap);
+      return buildNonOkResponse(

Review comment:
       it looks like all callers of `buildNonOkResponse` are calling 
`transformIfNeeded`, it makes sense to update the signature to just accept 
`SanitizableException`

##########
File path: 
server/src/main/java/org/apache/druid/server/initialization/ServerConfig.java
##########
@@ -235,14 +257,15 @@ public boolean equals(Object o)
            queueSize == that.queueSize &&
            enableRequestLimit == that.enableRequestLimit &&
            defaultQueryTimeout == that.defaultQueryTimeout &&
-           maxScatterGatherBytes.equals(that.maxScatterGatherBytes) &&
            maxSubqueryRows == that.maxSubqueryRows &&
            maxQueryTimeout == that.maxQueryTimeout &&
            maxRequestHeaderSize == that.maxRequestHeaderSize &&
            inflateBufferSize == that.inflateBufferSize &&
            compressionLevel == that.compressionLevel &&
            enableForwardedRequestCustomizer == 
that.enableForwardedRequestCustomizer &&
+           sanitizeJettyErrorResponse == that.sanitizeJettyErrorResponse &&

Review comment:
       hm, I wonder if technically `ErrorResponseTransformStrategy` should 
implement equals and be checked here

##########
File path: 
core/src/main/java/org/apache/druid/common/exception/SanitizableException.java
##########
@@ -0,0 +1,33 @@
+/*
+ * 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.common.exception;
+
+import java.util.function.Function;
+
+public interface SanitizableException
+{
+  /**
+   * Apply the function for transforming the error message then
+   * return new Exception with sanitized fields and transformed message.
+   */
+  Exception applyErrorMessageTransformAndSanitizeFields(

Review comment:
       nit: i think `transform` would be descriptive enough of a method name, 
between the parameter name and the javadocs.

##########
File path: 
core/src/main/java/org/apache/druid/common/exception/SanitizableException.java
##########
@@ -0,0 +1,33 @@
+/*
+ * 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.common.exception;
+
+import java.util.function.Function;
+
+public interface SanitizableException

Review comment:
       nit: if a longer term goal is to make sure all surface exceptions are 
wrapped in this to allow operators to tidy up the error responses, i wonder if 
we should take a stronger name for this interface like `ServiceApiException` or 
.. idk naming is hard.

##########
File path: sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java
##########
@@ -968,6 +973,44 @@ public void testUnsupportedQueryThrowsException() throws 
Exception
     Assert.assertTrue(lifecycleManager.getAll("id").isEmpty());
   }
 
+  @Test
+  public void testUnsupportedQueryThrowsExceptionWithFilterResponse() throws 
Exception
+  {
+    resource = new SqlResource(
+        JSON_MAPPER,
+        CalciteTests.TEST_AUTHORIZER_MAPPER,
+        sqlLifecycleFactory,
+        lifecycleManager,
+        new ServerConfig() {
+          @Override
+          public boolean isSanitizeJettyErrorResponse()
+          {
+            return true;
+          }
+          @Override
+          public ErrorResponseTransformStrategy 
getErrorResponseTransformStrategy()
+          {
+            return new 
AllowedRegexErrorResponseTransformStrategy(ImmutableList.of());
+          }
+        }
+    );
+
+    String errorMessage = "This will be support in Druid 9999";

Review comment:
       <img src="https://i.imgur.com/VlP5fgB.gif";></img>

##########
File path: sql/src/main/java/org/apache/druid/sql/http/SqlResource.java
##########
@@ -170,39 +174,50 @@ public Response doPost(
     }
     catch (QueryCapacityExceededException cap) {
       endLifecycle(sqlQueryId, lifecycle, cap, remoteAddr, -1);
-      return buildNonOkResponse(QueryCapacityExceededException.STATUS_CODE, 
cap);
+      return buildNonOkResponse(
+          QueryCapacityExceededException.STATUS_CODE,
+          
serverConfig.getErrorResponseTransformStrategy().transformIfNeeded(cap)
+      );
     }
     catch (QueryUnsupportedException unsupported) {
       endLifecycle(sqlQueryId, lifecycle, unsupported, remoteAddr, -1);
-      return buildNonOkResponse(QueryUnsupportedException.STATUS_CODE, 
unsupported);
+      return buildNonOkResponse(
+          QueryUnsupportedException.STATUS_CODE,
+          
serverConfig.getErrorResponseTransformStrategy().transformIfNeeded(unsupported)
+      );
     }
     catch (QueryTimeoutException timeout) {
       endLifecycle(sqlQueryId, lifecycle, timeout, remoteAddr, -1);
-      return buildNonOkResponse(QueryTimeoutException.STATUS_CODE, timeout);
+      return buildNonOkResponse(
+          QueryTimeoutException.STATUS_CODE,
+          
serverConfig.getErrorResponseTransformStrategy().transformIfNeeded(timeout)
+      );
     }
     catch (SqlPlanningException | ResourceLimitExceededException e) {
       endLifecycle(sqlQueryId, lifecycle, e, remoteAddr, -1);
-      return buildNonOkResponse(BadQueryException.STATUS_CODE, e);
+      return buildNonOkResponse(
+          BadQueryException.STATUS_CODE,
+          serverConfig.getErrorResponseTransformStrategy().transformIfNeeded(e)
+      );
     }
     catch (ForbiddenException e) {
       endLifecycleWithoutEmittingMetrics(sqlQueryId, lifecycle);
-      throw e; // let ForbiddenExceptionMapper handle this
+      throw (ForbiddenException) 
serverConfig.getErrorResponseTransformStrategy().transformIfNeeded(e); // let 
ForbiddenExceptionMapper handle this

Review comment:
       in fact, whenever in the future we do that, it might also make sense to 
move `QueryException` handling out to an `ExceptionMapper<QueryException>` and 
just rethrow all exceptions so that error response handling can be shared 
between sql and native json queries. it doesn't gain too much since will still 
need to handle internally so that metrics can be emitted, but it would cover 
the response writing.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]



---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to