This is an automated email from the ASF dual-hosted git repository.

adarshsanjeev pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git


The following commit(s) were added to refs/heads/master by this push:
     new 42c1117000b Add a new strategy to sanitize error messages based on the 
error message role (#18487)
42c1117000b is described below

commit 42c1117000b7a1c924026126c94d09a841bd4313
Author: Adarsh Sanjeev <[email protected]>
AuthorDate: Wed Sep 17 10:31:00 2025 +0530

    Add a new strategy to sanitize error messages based on the error message 
role (#18487)
    
    Add a new error message strategy to log the message and return an error id 
to the user, based on the role of the Druid exception.
    
    To enable this strategy, set 
druid.server.http.errorResponseTransform.strategy to persona.
---
 docs/configuration/index.md                        |  6 ++
 .../msq/sql/resources/SqlStatementResource.java    | 12 +++-
 .../druid/msq/sql/resources/SqlTaskResource.java   | 12 +++-
 .../dart/controller/http/DartSqlResourceTest.java  |  3 +-
 .../resources/SqlMSQStatementResourcePostTest.java |  7 +-
 .../sql/resources/SqlStatementResourceTest.java    |  4 +-
 .../exception/ErrorResponseTransformStrategy.java  | 16 ++++-
 .../PersonaBasedErrorTransformStrategy.java        | 77 ++++++++++++++++++++++
 .../common/exception/SanitizableException.java     |  4 +-
 .../java/util/emitter/service/AlertBuilder.java    |  2 +-
 .../PersonaBasedErrorTransformStrategyTest.java    | 71 ++++++++++++++++++++
 .../org/apache/druid/sql/avatica/ErrorHandler.java | 23 ++++++-
 .../org/apache/druid/sql/http/SqlResource.java     | 30 +++++++--
 .../org/apache/druid/sql/http/SqlResourceTest.java |  9 ++-
 14 files changed, 255 insertions(+), 21 deletions(-)

diff --git a/docs/configuration/index.md b/docs/configuration/index.md
index 394883a1b9b..c925fe964c1 100644
--- a/docs/configuration/index.md
+++ b/docs/configuration/index.md
@@ -627,6 +627,12 @@ On the other hand, if 
`druid.server.http.errorResponseTransform.allowedRegex` is
 {"error":"Plan validation 
failed","errorMessage":"org.apache.calcite.runtime.CalciteContextException: 
From line 1, column 15 to line 1, column 38: Object 'nonexistent-datasource' 
not found","errorClass":null,"host":null}
 ```
 
+##### Persona based error response transform strategy
+
+In this mode, Druid transforms any exceptions which are targeted at non-users 
personas. Instead of returning such exception directly, the strategy logs the 
exception against a random id and returns the id along with a generic error 
message to the user.
+
+To enable this strategy, set 
`druid.server.http.errorResponseTransform.strategy` to `persona`.
+
 ### Overlord discovery
 
 This config is used to find the [Overlord](../design/overlord.md) using 
Curator service discovery. Only required if you are actually running an 
Overlord.
diff --git 
a/multi-stage-query/src/main/java/org/apache/druid/msq/sql/resources/SqlStatementResource.java
 
b/multi-stage-query/src/main/java/org/apache/druid/msq/sql/resources/SqlStatementResource.java
index d480264e003..28e5f257f09 100644
--- 
a/multi-stage-query/src/main/java/org/apache/druid/msq/sql/resources/SqlStatementResource.java
+++ 
b/multi-stage-query/src/main/java/org/apache/druid/msq/sql/resources/SqlStatementResource.java
@@ -76,6 +76,7 @@ import org.apache.druid.query.QueryException;
 import org.apache.druid.rpc.HttpResponseException;
 import org.apache.druid.rpc.indexing.OverlordClient;
 import org.apache.druid.server.QueryResponse;
+import org.apache.druid.server.initialization.ServerConfig;
 import org.apache.druid.server.security.Action;
 import org.apache.druid.server.security.AuthenticationResult;
 import org.apache.druid.server.security.AuthorizationResult;
@@ -134,6 +135,7 @@ public class SqlStatementResource
   private final StorageConnector storageConnector;
   private final AuthorizerMapper authorizerMapper;
   private final DefaultQueryConfig defaultQueryConfig;
+  private final ServerConfig serverConfig;
 
   @Inject
   public SqlStatementResource(
@@ -142,7 +144,8 @@ public class SqlStatementResource
       final OverlordClient overlordClient,
       final @MultiStageQuery StorageConnectorProvider storageConnectorProvider,
       final AuthorizerMapper authorizerMapper,
-      final DefaultQueryConfig defaultQueryConfig
+      final DefaultQueryConfig defaultQueryConfig,
+      final ServerConfig serverConfig
   )
   {
     this.msqSqlStatementFactory = msqSqlStatementFactory;
@@ -151,6 +154,7 @@ public class SqlStatementResource
     this.storageConnector = 
storageConnectorProvider.createStorageConnector(null);
     this.authorizerMapper = authorizerMapper;
     this.defaultQueryConfig = defaultQueryConfig;
+    this.serverConfig = serverConfig;
   }
 
   /**
@@ -200,7 +204,11 @@ public class SqlStatementResource
       stmt = msqSqlStatementFactory.httpStatement(sqlQueryPlus, req);
     }
     catch (Exception e) {
-      return SqlResource.handleExceptionBeforeStatementCreated(e, 
sqlQuery.queryContext());
+      return SqlResource.handleExceptionBeforeStatementCreated(
+          e,
+          sqlQuery.queryContext(),
+          serverConfig.getErrorResponseTransformStrategy()
+      );
     }
 
     final String sqlQueryId = stmt.sqlQueryId();
diff --git 
a/multi-stage-query/src/main/java/org/apache/druid/msq/sql/resources/SqlTaskResource.java
 
b/multi-stage-query/src/main/java/org/apache/druid/msq/sql/resources/SqlTaskResource.java
index 9336e7baec2..75c064b30bf 100644
--- 
a/multi-stage-query/src/main/java/org/apache/druid/msq/sql/resources/SqlTaskResource.java
+++ 
b/multi-stage-query/src/main/java/org/apache/druid/msq/sql/resources/SqlTaskResource.java
@@ -37,6 +37,7 @@ import org.apache.druid.query.DefaultQueryConfig;
 import org.apache.druid.query.QueryException;
 import org.apache.druid.query.http.SqlTaskStatus;
 import org.apache.druid.server.QueryResponse;
+import org.apache.druid.server.initialization.ServerConfig;
 import org.apache.druid.server.security.Access;
 import org.apache.druid.server.security.AuthorizationUtils;
 import org.apache.druid.server.security.ForbiddenException;
@@ -82,17 +83,20 @@ public class SqlTaskResource
   private final SqlStatementFactory sqlStatementFactory;
   private final DefaultQueryConfig defaultQueryConfig;
   private final ObjectMapper jsonMapper;
+  private final ServerConfig serverConfig;
 
   @Inject
   public SqlTaskResource(
       final @MultiStageQuery SqlStatementFactory sqlStatementFactory,
       final DefaultQueryConfig defaultQueryConfig,
-      final ObjectMapper jsonMapper
+      final ObjectMapper jsonMapper,
+      final ServerConfig serverConfig
   )
   {
     this.sqlStatementFactory = sqlStatementFactory;
     this.defaultQueryConfig = defaultQueryConfig;
     this.jsonMapper = jsonMapper;
+    this.serverConfig = serverConfig;
   }
 
   /**
@@ -131,7 +135,11 @@ public class SqlTaskResource
       stmt = sqlStatementFactory.httpStatement(sqlQueryPlus, req);
     }
     catch (Exception e) {
-      return SqlResource.handleExceptionBeforeStatementCreated(e, 
sqlQuery.queryContext());
+      return SqlResource.handleExceptionBeforeStatementCreated(
+          e,
+          sqlQuery.queryContext(),
+          serverConfig.getErrorResponseTransformStrategy()
+      );
     }
 
     final String sqlQueryId = stmt.sqlQueryId();
diff --git 
a/multi-stage-query/src/test/java/org/apache/druid/msq/dart/controller/http/DartSqlResourceTest.java
 
b/multi-stage-query/src/test/java/org/apache/druid/msq/dart/controller/http/DartSqlResourceTest.java
index eba37c273a7..48d4cab9450 100644
--- 
a/multi-stage-query/src/test/java/org/apache/druid/msq/dart/controller/http/DartSqlResourceTest.java
+++ 
b/multi-stage-query/src/test/java/org/apache/druid/msq/dart/controller/http/DartSqlResourceTest.java
@@ -274,7 +274,8 @@ public class DartSqlResourceTest extends MSQTestBase
             ResponseContextConfig.newConfig(false),
             SELF_NODE
         ),
-        DefaultQueryConfig.NIL
+        DefaultQueryConfig.NIL,
+        new ServerConfig()
     );
 
     // Setup mocks
diff --git 
a/multi-stage-query/src/test/java/org/apache/druid/msq/sql/resources/SqlMSQStatementResourcePostTest.java
 
b/multi-stage-query/src/test/java/org/apache/druid/msq/sql/resources/SqlMSQStatementResourcePostTest.java
index 80c40725c89..4784dfea377 100644
--- 
a/multi-stage-query/src/test/java/org/apache/druid/msq/sql/resources/SqlMSQStatementResourcePostTest.java
+++ 
b/multi-stage-query/src/test/java/org/apache/druid/msq/sql/resources/SqlMSQStatementResourcePostTest.java
@@ -44,6 +44,7 @@ import org.apache.druid.query.DefaultQueryConfig;
 import org.apache.druid.query.ExecutionMode;
 import org.apache.druid.query.QueryContexts;
 import org.apache.druid.segment.column.ValueType;
+import org.apache.druid.server.initialization.ServerConfig;
 import org.apache.druid.sql.calcite.util.CalciteTests;
 import org.apache.druid.sql.http.ResultFormat;
 import org.apache.druid.sql.http.SqlQuery;
@@ -77,7 +78,8 @@ public class SqlMSQStatementResourcePostTest extends 
MSQTestBase
         indexingServiceClient,
         s -> localFileStorageConnector,
         authorizerMapper,
-        new DefaultQueryConfig(Map.of("debug", "false"))
+        new DefaultQueryConfig(Map.of("debug", "false")),
+        new ServerConfig()
     );
   }
 
@@ -332,7 +334,8 @@ public class SqlMSQStatementResourcePostTest extends 
MSQTestBase
         indexingServiceClient,
         s -> NilStorageConnector.getInstance(),
         authorizerMapper,
-        DefaultQueryConfig.NIL
+        DefaultQueryConfig.NIL,
+        new ServerConfig()
     );
 
     String errorMessage = "The sql statement api cannot read from the select 
destination [durableStorage] provided in "
diff --git 
a/multi-stage-query/src/test/java/org/apache/druid/msq/sql/resources/SqlStatementResourceTest.java
 
b/multi-stage-query/src/test/java/org/apache/druid/msq/sql/resources/SqlStatementResourceTest.java
index af21a85615f..d71bc4501c4 100644
--- 
a/multi-stage-query/src/test/java/org/apache/druid/msq/sql/resources/SqlStatementResourceTest.java
+++ 
b/multi-stage-query/src/test/java/org/apache/druid/msq/sql/resources/SqlStatementResourceTest.java
@@ -73,6 +73,7 @@ import org.apache.druid.segment.TestHelper;
 import org.apache.druid.segment.column.ColumnType;
 import org.apache.druid.segment.column.RowSignature;
 import org.apache.druid.segment.column.ValueType;
+import org.apache.druid.server.initialization.ServerConfig;
 import org.apache.druid.server.mocks.MockHttpServletRequest;
 import org.apache.druid.server.security.Access;
 import org.apache.druid.server.security.Action;
@@ -700,7 +701,8 @@ public class SqlStatementResourceTest extends MSQTestBase
         overlordClient,
         tempDir -> localFileStorageConnector,
         authorizerMapper,
-        new DefaultQueryConfig(Map.of("debug", "true"))
+        new DefaultQueryConfig(Map.of("debug", "true")),
+        new ServerConfig()
     );
   }
 
diff --git 
a/processing/src/main/java/org/apache/druid/common/exception/ErrorResponseTransformStrategy.java
 
b/processing/src/main/java/org/apache/druid/common/exception/ErrorResponseTransformStrategy.java
index d7a2bbd3768..b48ba97ca90 100644
--- 
a/processing/src/main/java/org/apache/druid/common/exception/ErrorResponseTransformStrategy.java
+++ 
b/processing/src/main/java/org/apache/druid/common/exception/ErrorResponseTransformStrategy.java
@@ -21,14 +21,17 @@ package org.apache.druid.common.exception;
 
 import com.fasterxml.jackson.annotation.JsonSubTypes;
 import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import org.apache.druid.error.DruidException;
 
 import javax.validation.constraints.NotNull;
+import java.util.Optional;
 import java.util.function.Function;
 
 @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "strategy", defaultImpl = 
NoErrorResponseTransformStrategy.class)
 @JsonSubTypes(value = {
     @JsonSubTypes.Type(name = "none", value = 
NoErrorResponseTransformStrategy.class),
-    @JsonSubTypes.Type(name = "allowedRegex", value = 
AllowedRegexErrorResponseTransformStrategy.class)
+    @JsonSubTypes.Type(name = "allowedRegex", value = 
AllowedRegexErrorResponseTransformStrategy.class),
+    @JsonSubTypes.Type(name = "persona", value = 
PersonaBasedErrorTransformStrategy.class),
 })
 public interface ErrorResponseTransformStrategy
 {
@@ -41,6 +44,17 @@ public interface ErrorResponseTransformStrategy
     return exception.sanitize(getErrorMessageTransformFunction());
   }
 
+  /**
+   * For a given {@link DruidException} apply the transformation strategy and 
return a sanitized Exception
+   * if the transformation stategy was applied. This call does not log the 
exception.
+   * It is the callers responsibility to do so. Returns Optional.empty() if no 
transformation was applied.
+   * The errorId is provided to be used in the transformed Exception if needed.
+   */
+  default Optional<Exception> maybeTransform(DruidException exception, 
Optional<String> errorId)
+  {
+    return Optional.empty();
+  }
+
   /**
    * Return a function for checking and transforming the error message if 
needed.
    * Function can return null if error message needs to be omitted or return 
String to be use instead.
diff --git 
a/processing/src/main/java/org/apache/druid/common/exception/PersonaBasedErrorTransformStrategy.java
 
b/processing/src/main/java/org/apache/druid/common/exception/PersonaBasedErrorTransformStrategy.java
new file mode 100644
index 00000000000..a0122204f79
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/common/exception/PersonaBasedErrorTransformStrategy.java
@@ -0,0 +1,77 @@
+/*
+ * 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.error.DruidException;
+import org.apache.druid.java.util.common.StringUtils;
+
+import java.util.Optional;
+import java.util.UUID;
+import java.util.function.Function;
+
+/**
+ * {@link ErrorResponseTransformStrategy} that modifies the error message of a 
{@link DruidException} based on the
+ * persona. For non-user error messages, this logs the exception with a 
randomly generated id and returns a new exception
+ * containing the id instead.
+ */
+public class PersonaBasedErrorTransformStrategy implements 
ErrorResponseTransformStrategy
+{
+  private static final String ERROR_WITH_ID_TEMPLATE = "Internal server error, 
please contact your administrator "
+                                                       + "with Error ID [%s] 
if the issue persists.";
+  public static final PersonaBasedErrorTransformStrategy INSTANCE = new 
PersonaBasedErrorTransformStrategy();
+
+  /**
+   * Transforms the {@link DruidException} if required. Returns an optional 
with a new Druid exception if the
+   * exception was modified. Returns an empty optional if no transformation 
was performed.
+   */
+  @Override
+  public Optional<Exception> maybeTransform(DruidException druidException, 
Optional<String> optionalErrorId)
+  {
+    if (druidException.getTargetPersona() == DruidException.Persona.USER) {
+      return Optional.empty();
+    }
+    String errorId = optionalErrorId.orElse(UUID.randomUUID().toString());
+
+    return Optional.of(DruidException.forPersona(DruidException.Persona.USER)
+                                     
.ofCategory(DruidException.Category.RUNTIME_FAILURE)
+                                     
.build(StringUtils.format(ERROR_WITH_ID_TEMPLATE, errorId)));
+  }
+
+  @Override
+  public Function<String, String> getErrorMessageTransformFunction()
+  {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean equals(Object o)
+  {
+    if (this == o) {
+      return true;
+    }
+    return !(o == null || getClass() != o.getClass());
+  }
+
+  @Override
+  public int hashCode()
+  {
+    return PersonaBasedErrorTransformStrategy.class.hashCode();
+  }
+}
diff --git 
a/processing/src/main/java/org/apache/druid/common/exception/SanitizableException.java
 
b/processing/src/main/java/org/apache/druid/common/exception/SanitizableException.java
index 2ac15e05eb8..b34eaf9521f 100644
--- 
a/processing/src/main/java/org/apache/druid/common/exception/SanitizableException.java
+++ 
b/processing/src/main/java/org/apache/druid/common/exception/SanitizableException.java
@@ -25,10 +25,10 @@ public interface SanitizableException
 {
   /**
    * Apply the function for transforming the error message then return new 
Exception with sanitized fields and transformed message.
-   * The {@param errorMessageTransformFunction} is only intended to be use to 
transform the error message
+   * The {@param errorMessageTransformFunction} is only intended to be used to 
transform the error message
    * String of the Exception as only the error message String is common to all 
Exception classes.
    * For other fields (which may be unique to each particular Exception 
class), each implementation of this method can
-   * decide for itself how to sanitized those fields (i.e. leaving unchanged, 
changing to null, changing to a fixed String, etc.).
+   * decide for itself how to sanitize those fields (i.e. leaving unchanged, 
changing to null, changing to a fixed String, etc.).
    * Note that this method returns a new Exception of the same type since 
Exception error message is immutable.
    */
   Exception sanitize(
diff --git 
a/processing/src/main/java/org/apache/druid/java/util/emitter/service/AlertBuilder.java
 
b/processing/src/main/java/org/apache/druid/java/util/emitter/service/AlertBuilder.java
index 352272d67bc..27c4144585e 100644
--- 
a/processing/src/main/java/org/apache/druid/java/util/emitter/service/AlertBuilder.java
+++ 
b/processing/src/main/java/org/apache/druid/java/util/emitter/service/AlertBuilder.java
@@ -69,7 +69,7 @@ public class AlertBuilder extends 
ServiceEventBuilder<AlertEvent>
     return this;
   }
 
-  public AlertBuilder addData(Map<String, Object> data)
+  public AlertBuilder addData(Map<String, ?> data)
   {
     dataMap.putAll(data);
     return this;
diff --git 
a/processing/src/test/java/org/apache/druid/common/exception/PersonaBasedErrorTransformStrategyTest.java
 
b/processing/src/test/java/org/apache/druid/common/exception/PersonaBasedErrorTransformStrategyTest.java
new file mode 100644
index 00000000000..9eb64ada0f1
--- /dev/null
+++ 
b/processing/src/test/java/org/apache/druid/common/exception/PersonaBasedErrorTransformStrategyTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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 nl.jqno.equalsverifier.EqualsVerifier;
+import org.apache.druid.error.DruidException;
+import org.apache.druid.error.DruidExceptionMatcher;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Optional;
+
+public class PersonaBasedErrorTransformStrategyTest
+{
+  private PersonaBasedErrorTransformStrategy target;
+
+  @Before
+  public void setUp() throws Exception
+  {
+    target = new PersonaBasedErrorTransformStrategy();
+  }
+
+  @Test
+  public void testUserPersonaRemainsUnchanged()
+  {
+    DruidException druidException = 
DruidException.forPersona(DruidException.Persona.USER)
+                                                  
.ofCategory(DruidException.Category.FORBIDDEN)
+                                                  .build("Permission 
exception");
+    Assert.assertEquals(Optional.empty(), 
target.maybeTransform(druidException, Optional.empty()));
+  }
+
+  @Test
+  public void testDeveloperPersonaIsTransformed()
+  {
+    DruidException druidException = DruidException.defensive().build("Test 
Defensive exception");
+
+    DruidExceptionMatcher druidExceptionMatcher = new DruidExceptionMatcher(
+        DruidException.Persona.USER,
+        druidException.getCategory(),
+        druidException.getErrorCode()
+    ).expectMessageContains("Could not process the query, please contact your 
administrator with Error ID");
+
+    druidExceptionMatcher.matches(target.maybeTransform(druidException, 
Optional.of("the-error")).get());
+  }
+
+  @Test
+  public void testEqualsAndHashCode()
+  {
+    EqualsVerifier.forClass(PersonaBasedErrorTransformStrategy.class)
+                  .usingGetClass()
+                  .verify();
+  }
+}
diff --git a/sql/src/main/java/org/apache/druid/sql/avatica/ErrorHandler.java 
b/sql/src/main/java/org/apache/druid/sql/avatica/ErrorHandler.java
index a2daf4689ba..8e2927279ee 100644
--- a/sql/src/main/java/org/apache/druid/sql/avatica/ErrorHandler.java
+++ b/sql/src/main/java/org/apache/druid/sql/avatica/ErrorHandler.java
@@ -23,13 +23,19 @@ import com.google.inject.Inject;
 import org.apache.druid.common.exception.ErrorResponseTransformStrategy;
 import org.apache.druid.common.exception.NoErrorResponseTransformStrategy;
 import org.apache.druid.common.exception.SanitizableException;
+import org.apache.druid.error.DruidException;
 import org.apache.druid.java.util.common.ISE;
+import org.apache.druid.java.util.common.StringUtils;
 import org.apache.druid.java.util.common.UOE;
+import org.apache.druid.java.util.common.logger.Logger;
 import org.apache.druid.query.QueryException;
 import org.apache.druid.query.QueryInterruptedException;
 import org.apache.druid.server.initialization.ServerConfig;
 import org.apache.druid.server.security.ForbiddenException;
 
+import java.util.Optional;
+import java.util.UUID;
+
 
 /**
  * ErrorHandler is a utility class that is used to sanitize exceptions.
@@ -37,6 +43,7 @@ import org.apache.druid.server.security.ForbiddenException;
 class ErrorHandler
 {
   private final ErrorResponseTransformStrategy errorResponseTransformStrategy;
+  private static final Logger log = new Logger(ErrorHandler.class);
 
   @Inject
   ErrorHandler(final ServerConfig serverConfig)
@@ -79,6 +86,20 @@ class ErrorHandler
       // could do `throw sanitize(error);` but just sanitizing immediately 
avoids unnecessary going down multiple levels
       return new 
RuntimeException(errorResponseTransformStrategy.transformIfNeeded((SanitizableException)
 error.getCause()));
     }
+    if (error instanceof DruidException) {
+      String errorId = UUID.randomUUID().toString();
+      Optional<Exception> transformedException = 
errorResponseTransformStrategy.maybeTransform(
+          (DruidException) error,
+          Optional.of(errorId)
+      );
+
+      if (transformedException.isPresent()) {
+        // Log the exception here itself, since the error has been transformed.
+        log.error(error, StringUtils.format("External Error ID: [%s]", 
errorId));
+      }
+      QueryInterruptedException wrappedError = 
QueryInterruptedException.wrapIfNeeded(transformedException.orElse((Exception) 
error));
+      return (QueryException) 
errorResponseTransformStrategy.transformIfNeeded(wrappedError);
+    }
     QueryInterruptedException wrappedError = 
QueryInterruptedException.wrapIfNeeded(error);
     return (QueryException) 
errorResponseTransformStrategy.transformIfNeeded(wrappedError);
   }
@@ -86,7 +107,7 @@ class ErrorHandler
   /**
    * Check to see if something needs to be sanitized.
    * <p>
-   * Done by checking to see if the ErrorResponse is different than a NoOp 
Error response transform strategy.
+   * Done by checking to see if the ErrorResponse is different from a NoOp 
Error response transform strategy.
    *
    * @return a boolean that returns true if error handler has an error 
response strategy other than the NoOp error
    * response strategy
diff --git a/sql/src/main/java/org/apache/druid/sql/http/SqlResource.java 
b/sql/src/main/java/org/apache/druid/sql/http/SqlResource.java
index 57df7c18945..98e39e7be3a 100644
--- a/sql/src/main/java/org/apache/druid/sql/http/SqlResource.java
+++ b/sql/src/main/java/org/apache/druid/sql/http/SqlResource.java
@@ -24,6 +24,7 @@ import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableMap;
 import com.google.inject.Inject;
 import com.sun.jersey.api.core.HttpContext;
+import org.apache.druid.common.exception.ErrorResponseTransformStrategy;
 import org.apache.druid.error.DruidException;
 import org.apache.druid.java.util.common.StringUtils;
 import org.apache.druid.java.util.common.logger.Logger;
@@ -32,6 +33,7 @@ import org.apache.druid.query.QueryContext;
 import org.apache.druid.query.QueryContexts;
 import org.apache.druid.server.QueryResource;
 import org.apache.druid.server.QueryResultPusher;
+import org.apache.druid.server.initialization.ServerConfig;
 import org.apache.druid.server.security.Action;
 import org.apache.druid.server.security.AuthenticationResult;
 import org.apache.druid.server.security.AuthorizationResult;
@@ -64,7 +66,9 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
+import java.util.UUID;
 import java.util.stream.Collectors;
 
 @Path(SqlResource.PATH)
@@ -83,6 +87,7 @@ public class SqlResource
   private final SqlLifecycleManager sqlLifecycleManager;
   private final SqlEngineRegistry sqlEngineRegistry;
   private final DefaultQueryConfig defaultQueryConfig;
+  private final ServerConfig serverConfig;
 
   @VisibleForTesting
   @Inject
@@ -91,7 +96,8 @@ public class SqlResource
       final SqlLifecycleManager sqlLifecycleManager,
       final SqlEngineRegistry sqlEngineRegistry,
       final SqlResourceQueryResultPusherFactory resultPusherFactory,
-      final DefaultQueryConfig defaultQueryConfig
+      final DefaultQueryConfig defaultQueryConfig,
+      final ServerConfig serverConfig
   )
   {
     this.resultPusherFactory = resultPusherFactory;
@@ -99,7 +105,7 @@ public class SqlResource
     this.authorizerMapper = Preconditions.checkNotNull(authorizerMapper, 
"authorizerMapper");
     this.sqlLifecycleManager = Preconditions.checkNotNull(sqlLifecycleManager, 
"sqlLifecycleManager");
     this.defaultQueryConfig = Preconditions.checkNotNull(defaultQueryConfig, 
"defaultQueryConfig");
-
+    this.serverConfig = serverConfig;
   }
 
   @GET
@@ -186,7 +192,11 @@ public class SqlResource
     }
     catch (Exception e) {
       // Can't use the queryContext with SETs since it might not have been 
created yet. Use the original one.
-      return handleExceptionBeforeStatementCreated(e, sqlQuery.queryContext());
+      return handleExceptionBeforeStatementCreated(
+          e,
+          sqlQuery.queryContext(),
+          serverConfig.getErrorResponseTransformStrategy()
+      );
     }
 
     final String currThreadName = Thread.currentThread().getName();
@@ -296,12 +306,22 @@ public class SqlResource
   /**
    * Generates a response for a {@link DruidException} that occurs prior to 
the {@link HttpStatement} being created.
    */
-  public static Response handleExceptionBeforeStatementCreated(final Exception 
e, final QueryContext queryContext)
+  public static Response handleExceptionBeforeStatementCreated(
+      final Exception e,
+      final QueryContext queryContext,
+      final ErrorResponseTransformStrategy strategy
+  )
   {
     if (e instanceof DruidException) {
       final String sqlQueryId = 
queryContext.getString(QueryContexts.CTX_SQL_QUERY_ID);
+      String errorId = sqlQueryId == null ? UUID.randomUUID().toString() : 
sqlQueryId;
+      Optional<Exception> transformed = 
strategy.maybeTransform((DruidException) e, Optional.of(errorId));
+      if (transformed.isPresent()) {
+        // Log the exception here itself, since the error has been transformed.
+        log.error(e, StringUtils.format("External Error ID: [%s]", errorId));
+      }
       return QueryResultPusher.handleDruidExceptionBeforeResponseStarted(
-          (DruidException) e,
+          (DruidException) transformed.orElse(e),
           MediaType.APPLICATION_JSON_TYPE,
           sqlQueryId != null
           ? ImmutableMap.<String, String>builder()
diff --git a/sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java 
b/sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java
index 87e9285a30a..4f0d53dfd02 100644
--- a/sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java
+++ b/sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java
@@ -333,7 +333,8 @@ public class SqlResourceTest extends CalciteTestBase
             TEST_RESPONSE_CONTEXT_CONFIG,
             DUMMY_DRUID_NODE
         ),
-        DefaultQueryConfig.NIL
+        DefaultQueryConfig.NIL,
+        new ServerConfig()
     );
   }
 
@@ -609,7 +610,8 @@ public class SqlResourceTest extends CalciteTestBase
             TEST_RESPONSE_CONTEXT_CONFIG,
             DUMMY_DRUID_NODE
         ),
-        queryConfigWithTimezone
+        queryConfigWithTimezone,
+        new ServerConfig()
     );
 
     final List<Map<String, Object>> rows = doPost(
@@ -1720,7 +1722,8 @@ public class SqlResourceTest extends CalciteTestBase
             TEST_RESPONSE_CONTEXT_CONFIG,
             DUMMY_DRUID_NODE
         ),
-        DefaultQueryConfig.NIL
+        DefaultQueryConfig.NIL,
+        new ServerConfig()
     );
 
     String errorMessage = "This will be supported in Druid 9999";


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

Reply via email to