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

AlbericByte 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 4bb735c1d75 fix: return actionable errors for invalid native queries 
(#19775)
4bb735c1d75 is described below

commit 4bb735c1d7530a91b349d14ae553c3ee7a4156f4
Author: Frank Chen <[email protected]>
AuthorDate: Tue Jul 28 23:10:52 2026 +0800

    fix: return actionable errors for invalid native queries (#19775)
    
    * Return actionable errors for invalid native queries
    
    * Improve nested native query error messages
    
    * test: cover invalid native query error messages
---
 .../apache/druid/query/BadJsonQueryException.java  | 24 +++++++
 .../druid/query/BadJsonQueryExceptionTest.java     | 73 ++++++++++++++++++++++
 .../org/apache/druid/server/QueryResource.java     |  4 ++
 .../org/apache/druid/server/QueryResourceTest.java | 18 ++++++
 4 files changed, 119 insertions(+)

diff --git 
a/processing/src/main/java/org/apache/druid/query/BadJsonQueryException.java 
b/processing/src/main/java/org/apache/druid/query/BadJsonQueryException.java
index 56f1a25be89..7722bb70be5 100644
--- a/processing/src/main/java/org/apache/druid/query/BadJsonQueryException.java
+++ b/processing/src/main/java/org/apache/druid/query/BadJsonQueryException.java
@@ -22,6 +22,8 @@ package org.apache.druid.query;
 import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.exc.ValueInstantiationException;
+import com.google.common.base.Strings;
 
 public class BadJsonQueryException extends BadQueryException
 {
@@ -32,6 +34,11 @@ public class BadJsonQueryException extends BadQueryException
     this(e, JSON_PARSE_ERROR_CODE, e.getMessage(), ERROR_CLASS);
   }
 
+  public BadJsonQueryException(ValueInstantiationException e)
+  {
+    this(e, JSON_PARSE_ERROR_CODE, getErrorMessage(e), e.getClass().getName());
+  }
+
   @JsonCreator
   private BadJsonQueryException(
       @JsonProperty("error") String errorCode,
@@ -51,4 +58,21 @@ public class BadJsonQueryException extends BadQueryException
   {
     super(cause, errorCode, errorMessage, errorClass, null);
   }
+
+  private static String getErrorMessage(ValueInstantiationException e)
+  {
+    Throwable cause = e.getCause();
+    String errorMessage = null;
+    while (cause != null) {
+      if (!Strings.isNullOrEmpty(cause.getMessage())) {
+        errorMessage = cause.getMessage();
+      }
+      cause = cause.getCause();
+    }
+
+    if (errorMessage == null) {
+      return "Invalid native query: the request contains invalid or missing 
fields";
+    }
+    return "Invalid native query: " + errorMessage;
+  }
 }
diff --git 
a/processing/src/test/java/org/apache/druid/query/BadJsonQueryExceptionTest.java
 
b/processing/src/test/java/org/apache/druid/query/BadJsonQueryExceptionTest.java
new file mode 100644
index 00000000000..2e73eaed002
--- /dev/null
+++ 
b/processing/src/test/java/org/apache/druid/query/BadJsonQueryExceptionTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.query;
+
+import com.fasterxml.jackson.databind.exc.ValueInstantiationException;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class BadJsonQueryExceptionTest
+{
+  @Test
+  public void testUsesDeepestCauseMessage()
+  {
+    final BadJsonQueryException exception = new BadJsonQueryException(
+        valueInstantiationException(
+            new RuntimeException("intermediate wrapper", new 
IllegalArgumentException("actionable validation message"))
+        )
+    );
+
+    Assertions.assertEquals("Invalid native query: actionable validation 
message", exception.getMessage());
+  }
+
+  @Test
+  public void testIgnoresEmptyCauseMessage()
+  {
+    final BadJsonQueryException exception = new BadJsonQueryException(
+        valueInstantiationException(
+            new RuntimeException("actionable validation message", new 
IllegalArgumentException(""))
+        )
+    );
+
+    Assertions.assertEquals("Invalid native query: actionable validation 
message", exception.getMessage());
+  }
+
+  @Test
+  public void testUsesFallbackWithoutCause()
+  {
+    final BadJsonQueryException exception = new 
BadJsonQueryException(valueInstantiationException(null));
+
+    Assertions.assertEquals(
+        "Invalid native query: the request contains invalid or missing fields",
+        exception.getMessage()
+    );
+  }
+
+  private static ValueInstantiationException 
valueInstantiationException(Throwable cause)
+  {
+    return ValueInstantiationException.from(
+        null,
+        "Jackson wrapper",
+        TypeFactory.defaultInstance().constructType(Query.class),
+        cause
+    );
+  }
+}
diff --git a/server/src/main/java/org/apache/druid/server/QueryResource.java 
b/server/src/main/java/org/apache/druid/server/QueryResource.java
index 80ea2895b97..eabf83cfa73 100644
--- a/server/src/main/java/org/apache/druid/server/QueryResource.java
+++ b/server/src/main/java/org/apache/druid/server/QueryResource.java
@@ -23,6 +23,7 @@ import com.fasterxml.jackson.core.JsonGenerator;
 import com.fasterxml.jackson.core.JsonParseException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.exc.ValueInstantiationException;
 import com.fasterxml.jackson.jaxrs.smile.SmileMediaTypes;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.Iterables;
@@ -260,6 +261,9 @@ public class QueryResource implements 
QueryCountStatsProvider
     try {
       baseQuery = ioReaderWriter.getRequestMapper().readValue(in, Query.class);
     }
+    catch (ValueInstantiationException e) {
+      throw new BadJsonQueryException(e);
+    }
     catch (JsonParseException e) {
       throw new BadJsonQueryException(e);
     }
diff --git 
a/server/src/test/java/org/apache/druid/server/QueryResourceTest.java 
b/server/src/test/java/org/apache/druid/server/QueryResourceTest.java
index d7a611bfc32..671c50c5edc 100644
--- a/server/src/test/java/org/apache/druid/server/QueryResourceTest.java
+++ b/server/src/test/java/org/apache/druid/server/QueryResourceTest.java
@@ -22,6 +22,7 @@ package org.apache.druid.server;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.exc.ValueInstantiationException;
 import com.fasterxml.jackson.jaxrs.smile.SmileMediaTypes;
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;
@@ -1043,6 +1044,23 @@ public class QueryResourceTest
     Assert.assertEquals(BadJsonQueryException.ERROR_CLASS, e.getErrorClass());
   }
 
+  @Test
+  public void testIncompleteQuery() throws IOException
+  {
+    final Response response = queryResource.doPost(
+        new 
ByteArrayInputStream("{\"queryType\":\"scan\"}".getBytes(StandardCharsets.UTF_8)),
+        null /*pretty*/,
+        testServletRequest
+    );
+
+    Assert.assertNotNull(response);
+    Assert.assertEquals(Status.BAD_REQUEST.getStatusCode(), 
response.getStatus());
+    final QueryException e = jsonMapper.readValue((byte[]) 
response.getEntity(), QueryException.class);
+    Assert.assertEquals(QueryException.JSON_PARSE_ERROR_CODE, 
e.getErrorCode());
+    Assert.assertEquals(ValueInstantiationException.class.getName(), 
e.getErrorClass());
+    Assert.assertEquals("Invalid native query: dataSource can't be null", 
e.getMessage());
+  }
+
   @Test
   public void testResourceLimitExceeded() throws IOException
   {


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

Reply via email to