joda-time
joda-time
diff --git a/sql/src/main/java/org/apache/druid/sql/avatica/DruidAvaticaHandler.java b/sql/src/main/java/org/apache/druid/sql/avatica/DruidAvaticaHandler.java
new file mode 100644
index 000000000000..867f7734988a
--- /dev/null
+++ b/sql/src/main/java/org/apache/druid/sql/avatica/DruidAvaticaHandler.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.sql.avatica;
+
+import org.apache.calcite.avatica.metrics.MetricsSystem;
+import org.apache.calcite.avatica.metrics.Timer;
+import org.apache.calcite.avatica.remote.LocalService;
+import org.apache.calcite.avatica.remote.MetricsHelper;
+import org.apache.calcite.avatica.remote.Service;
+import org.apache.calcite.avatica.server.MetricsAwareAvaticaHandler;
+import org.apache.calcite.avatica.util.UnsynchronizedBuffer;
+import org.eclipse.jetty.server.Handler;
+
+/**
+ * Base class for Druid's custom Avatica server handlers that are compatible with Jetty 12.
+ *
+ * This class provides a Jetty 12-compatible implementation of {@link MetricsAwareAvaticaHandler}
+ * to enable Druid's JDBC support via Apache Calcite's Avatica server. Since Calcite's Avatica server
+ * does not natively support Jetty 12, this custom implementation allows Druid to continue using
+ * Avatica for JDBC connectivity during the Jetty 12 migration.
+ *
+ *
Concrete implementations handle different wire protocols:
+ *
+ * - {@link DruidAvaticaJsonHandler} - JSON-based protocol
+ * - {@link DruidAvaticaProtobufHandler} - Protocol Buffers-based protocol
+ *
+ */
+public abstract class DruidAvaticaHandler extends Handler.Abstract implements MetricsAwareAvaticaHandler
+{
+ protected final Service service;
+ protected final MetricsSystem metrics;
+ protected final Timer requestTimer;
+ protected final ThreadLocal threadLocalBuffer;
+
+ protected DruidAvaticaHandler(
+ final DruidMeta druidMeta,
+ final AvaticaMonitor avaticaMonitor,
+ final Class> timerClass
+ )
+ {
+ this.service = new LocalService(druidMeta);
+ this.metrics = avaticaMonitor;
+ this.threadLocalBuffer = ThreadLocal.withInitial(UnsynchronizedBuffer::new);
+ this.requestTimer = this.metrics.getTimer(
+ MetricsHelper.concat(timerClass, MetricsAwareAvaticaHandler.REQUEST_TIMER_NAME)
+ );
+ }
+
+ @Override
+ public MetricsSystem getMetrics()
+ {
+ return metrics;
+ }
+
+ @Override
+ public void setServerRpcMetadata(Service.RpcMetadataResponse metadata)
+ {
+ service.setRpcMetadata(metadata);
+ }
+}
diff --git a/sql/src/main/java/org/apache/druid/sql/avatica/DruidAvaticaJsonHandler.java b/sql/src/main/java/org/apache/druid/sql/avatica/DruidAvaticaJsonHandler.java
index 4f1a5818bf45..f6d0738e2321 100644
--- a/sql/src/main/java/org/apache/druid/sql/avatica/DruidAvaticaJsonHandler.java
+++ b/sql/src/main/java/org/apache/druid/sql/avatica/DruidAvaticaJsonHandler.java
@@ -19,25 +19,37 @@
package org.apache.druid.sql.avatica;
-import com.google.inject.Inject;
-import org.apache.calcite.avatica.remote.LocalService;
+import org.apache.calcite.avatica.AvaticaUtils;
+import org.apache.calcite.avatica.metrics.Timer;
+import org.apache.calcite.avatica.remote.JsonHandler;
import org.apache.calcite.avatica.remote.Service;
import org.apache.calcite.avatica.server.AvaticaJsonHandler;
+import org.apache.calcite.avatica.util.UnsynchronizedBuffer;
import org.apache.druid.guice.annotations.Self;
import org.apache.druid.java.util.common.StringUtils;
+import org.apache.druid.java.util.common.logger.Logger;
import org.apache.druid.server.DruidNode;
+import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.Callback;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
+import javax.inject.Inject;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
-public class DruidAvaticaJsonHandler extends AvaticaJsonHandler
+public class DruidAvaticaJsonHandler extends DruidAvaticaHandler
{
+ private static final Logger LOG = new Logger(DruidAvaticaJsonHandler.class);
+
public static final String AVATICA_PATH_NO_TRAILING_SLASH = "/druid/v2/sql/avatica";
public static final String AVATICA_PATH = AVATICA_PATH_NO_TRAILING_SLASH + "/";
+
+ private final JsonHandler jsonHandler;
+
@Inject
public DruidAvaticaJsonHandler(
final DruidMeta druidMeta,
@@ -45,20 +57,72 @@ public DruidAvaticaJsonHandler(
final AvaticaMonitor avaticaMonitor
)
{
- super(new LocalService(druidMeta), avaticaMonitor);
+ super(druidMeta, Objects.requireNonNull(avaticaMonitor), AvaticaJsonHandler.class);
+ this.jsonHandler = new JsonHandler(service, this.metrics);
setServerRpcMetadata(new Service.RpcMetadataResponse(druidNode.getHostAndPortToUse()));
}
@Override
- public void handle(
- final String target,
- final Request baseRequest,
- final HttpServletRequest request,
- final HttpServletResponse response
- ) throws IOException, ServletException
+ public boolean handle(Request request, Response response, Callback callback) throws Exception
+ {
+ String requestURI = request.getHttpURI().getPath();
+ try (Timer.Context ctx = this.requestTimer.start()) {
+ if (AVATICA_PATH_NO_TRAILING_SLASH.equals(StringUtils.maybeRemoveTrailingSlash(requestURI))) {
+ response.getHeaders().put("Content-Type", "application/json;charset=utf-8");
+
+ if (!"POST".equals(request.getMethod())) {
+ response.setStatus(405);
+ response.write(
+ true,
+ ByteBuffer.wrap("This server expects only POST calls.".getBytes(StandardCharsets.UTF_8)), callback
+ );
+ return true;
+ }
+
+ String rawRequest = request.getHeaders().get("request");
+ if (rawRequest == null) {
+ // Avoid a new buffer creation for every HTTP request
+ final UnsynchronizedBuffer buffer = threadLocalBuffer.get();
+ try (InputStream inputStream = Content.Source.asInputStream(request)) {
+ byte[] bytes = AvaticaUtils.readFullyToBytes(inputStream, buffer);
+ String encoding = request.getHeaders().get("Content-Encoding");
+ if (encoding == null) {
+ encoding = "UTF-8";
+ }
+ rawRequest = AvaticaUtils.newString(bytes, encoding);
+ }
+ finally {
+ // Reset the offset into the buffer after we're done
+ buffer.reset();
+ }
+ }
+ final String jsonRequest = rawRequest;
+ LOG.trace("request: %s", jsonRequest);
+
+ org.apache.calcite.avatica.remote.Handler.HandlerResponse jsonResponse;
+ try {
+ jsonResponse = jsonHandler.apply(jsonRequest);
+ }
+ catch (Exception e) {
+ LOG.debug(e, "Error invoking request");
+ jsonResponse = jsonHandler.convertToErrorResponse(e);
+ }
+
+ LOG.trace("response: %s", jsonResponse);
+ response.setStatus(jsonResponse.getStatusCode());
+ response.write(true, ByteBuffer.wrap(jsonResponse.getResponse().getBytes(StandardCharsets.UTF_8)), callback);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void setServerRpcMetadata(Service.RpcMetadataResponse metadata)
{
- if (AVATICA_PATH_NO_TRAILING_SLASH.equals(StringUtils.maybeRemoveTrailingSlash(request.getRequestURI()))) {
- super.handle(target, baseRequest, request, response);
+ super.setServerRpcMetadata(metadata);
+ if (jsonHandler != null) {
+ jsonHandler.setRpcMetadata(metadata);
}
}
}
diff --git a/sql/src/main/java/org/apache/druid/sql/avatica/DruidAvaticaProtobufHandler.java b/sql/src/main/java/org/apache/druid/sql/avatica/DruidAvaticaProtobufHandler.java
index a15efadda6f6..7d5c6183025a 100644
--- a/sql/src/main/java/org/apache/druid/sql/avatica/DruidAvaticaProtobufHandler.java
+++ b/sql/src/main/java/org/apache/druid/sql/avatica/DruidAvaticaProtobufHandler.java
@@ -19,46 +19,100 @@
package org.apache.druid.sql.avatica;
-import com.google.inject.Inject;
-import org.apache.calcite.avatica.remote.LocalService;
+import org.apache.calcite.avatica.AvaticaUtils;
+import org.apache.calcite.avatica.metrics.Timer;
+import org.apache.calcite.avatica.remote.ProtobufHandler;
+import org.apache.calcite.avatica.remote.ProtobufTranslation;
+import org.apache.calcite.avatica.remote.ProtobufTranslationImpl;
import org.apache.calcite.avatica.remote.Service;
import org.apache.calcite.avatica.server.AvaticaProtobufHandler;
+import org.apache.calcite.avatica.util.UnsynchronizedBuffer;
import org.apache.druid.guice.annotations.Self;
import org.apache.druid.java.util.common.StringUtils;
+import org.apache.druid.java.util.common.logger.Logger;
import org.apache.druid.server.DruidNode;
+import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.Callback;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
+import javax.inject.Inject;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
-public class DruidAvaticaProtobufHandler extends AvaticaProtobufHandler
+public class DruidAvaticaProtobufHandler extends DruidAvaticaHandler
{
+
+ private static final Logger LOG = new Logger(DruidAvaticaProtobufHandler.class);
+
public static final String AVATICA_PATH_NO_TRAILING_SLASH = "/druid/v2/sql/avatica-protobuf";
public static final String AVATICA_PATH = AVATICA_PATH_NO_TRAILING_SLASH + "/";
+ private final ProtobufHandler protobufHandler;
+
@Inject
public DruidAvaticaProtobufHandler(
final DruidMeta druidMeta,
@Self final DruidNode druidNode,
- final AvaticaMonitor avaticaMonitor
+ final AvaticaMonitor metrics
)
{
- super(new LocalService(druidMeta), avaticaMonitor);
+ super(druidMeta, metrics, AvaticaProtobufHandler.class);
+ ProtobufTranslation protobufTranslation = new ProtobufTranslationImpl();
+ this.protobufHandler = new ProtobufHandler(service, protobufTranslation, this.metrics);
setServerRpcMetadata(new Service.RpcMetadataResponse(druidNode.getHostAndPortToUse()));
}
@Override
- public void handle(
- final String target,
- final Request baseRequest,
- final HttpServletRequest request,
- final HttpServletResponse response
- ) throws IOException, ServletException
+ public boolean handle(Request request, Response response, Callback callback) throws Exception
+ {
+ String requestURI = request.getHttpURI().getPath();
+ if (AVATICA_PATH_NO_TRAILING_SLASH.equals(StringUtils.maybeRemoveTrailingSlash(requestURI))) {
+ try (Timer.Context ctx = this.requestTimer.start()) {
+ if (!"POST".equals(request.getMethod())) {
+ response.setStatus(405);
+ response.write(
+ true,
+ ByteBuffer.wrap("This server expects only POST calls.".getBytes(StandardCharsets.UTF_8)), callback
+ );
+ return true;
+ }
+ final byte[] requestBytes;
+ // Avoid a new buffer creation for every HTTP request
+ final UnsynchronizedBuffer buffer = threadLocalBuffer.get();
+ try (InputStream inputStream = Content.Source.asInputStream(request)) {
+ requestBytes = AvaticaUtils.readFullyToBytes(inputStream, buffer);
+ }
+ finally {
+ buffer.reset();
+ }
+
+ response.getHeaders().put("Content-Type", "application/octet-stream;charset=utf-8");
+
+ org.apache.calcite.avatica.remote.Handler.HandlerResponse handlerResponse;
+ try {
+ handlerResponse = protobufHandler.apply(requestBytes);
+ }
+ catch (Exception e) {
+ LOG.debug(e, "Error invoking request");
+ handlerResponse = protobufHandler.convertToErrorResponse(e);
+ }
+
+ response.setStatus(handlerResponse.getStatusCode());
+ response.write(true, ByteBuffer.wrap(handlerResponse.getResponse()), callback);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void setServerRpcMetadata(Service.RpcMetadataResponse metadata)
{
- if (AVATICA_PATH_NO_TRAILING_SLASH.equals(StringUtils.maybeRemoveTrailingSlash(request.getRequestURI()))) {
- super.handle(target, baseRequest, request, response);
+ super.setServerRpcMetadata(metadata);
+ if (protobufHandler != null) {
+ protobufHandler.setRpcMetadata(metadata);
}
}
}
diff --git a/sql/src/test/java/org/apache/druid/quidem/DruidAvaticaTestDriver.java b/sql/src/test/java/org/apache/druid/quidem/DruidAvaticaTestDriver.java
index f832ec80655f..a586358e1262 100644
--- a/sql/src/test/java/org/apache/druid/quidem/DruidAvaticaTestDriver.java
+++ b/sql/src/test/java/org/apache/druid/quidem/DruidAvaticaTestDriver.java
@@ -24,7 +24,6 @@
import com.google.inject.Injector;
import com.google.inject.Provides;
import com.google.inject.name.Named;
-import org.apache.calcite.avatica.server.AbstractAvaticaHandler;
import org.apache.druid.guice.LazySingleton;
import org.apache.druid.initialization.DruidModule;
import org.apache.druid.java.util.common.StringUtils;
@@ -32,6 +31,7 @@
import org.apache.druid.server.DruidNode;
import org.apache.druid.server.SpecificSegmentsQuerySegmentWalker;
import org.apache.druid.sql.avatica.AvaticaMonitor;
+import org.apache.druid.sql.avatica.DruidAvaticaHandler;
import org.apache.druid.sql.avatica.DruidAvaticaJsonHandler;
import org.apache.druid.sql.avatica.DruidMeta;
import org.apache.druid.sql.calcite.SqlTestFrameworkConfig;
@@ -185,7 +185,7 @@ public void close()
}
}
- protected AbstractAvaticaHandler getAvaticaHandler(final DruidMeta druidMeta)
+ protected DruidAvaticaHandler getAvaticaHandler(final DruidMeta druidMeta)
{
return new DruidAvaticaJsonHandler(
druidMeta,
diff --git a/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaHandlerTest.java b/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaHandlerTest.java
index 4ef05973389b..22ea225301ca 100644
--- a/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaHandlerTest.java
+++ b/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaHandlerTest.java
@@ -39,7 +39,6 @@
import org.apache.calcite.avatica.Meta;
import org.apache.calcite.avatica.MissingResultsException;
import org.apache.calcite.avatica.NoSuchStatementException;
-import org.apache.calcite.avatica.server.AbstractAvaticaHandler;
import org.apache.druid.guice.LazySingleton;
import org.apache.druid.guice.LifecycleModule;
import org.apache.druid.guice.StartupInjectorBuilder;
@@ -255,7 +254,7 @@ protected String getJdbcUrlTail()
}
// Default implementation is for JSON to allow debugging of tests.
- protected AbstractAvaticaHandler getAvaticaHandler(final DruidMeta druidMeta)
+ protected DruidAvaticaHandler getAvaticaHandler(final DruidMeta druidMeta)
{
return new DruidAvaticaJsonHandler(
druidMeta,
diff --git a/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaJsonHandlerTest.java b/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaJsonHandlerTest.java
new file mode 100644
index 000000000000..94bb835e0e99
--- /dev/null
+++ b/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaJsonHandlerTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.sql.avatica;
+
+import org.apache.druid.server.DruidNode;
+import org.easymock.EasyMock;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.Callback;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.nio.ByteBuffer;
+
+public class DruidAvaticaJsonHandlerTest extends DruidAvaticaHandlerTest
+{
+ @Override
+ protected String getJdbcUrlTail()
+ {
+ return DruidAvaticaJsonHandler.AVATICA_PATH;
+ }
+
+ @Override
+ protected DruidAvaticaHandler getAvaticaHandler(final DruidMeta druidMeta)
+ {
+ return new DruidAvaticaJsonHandler(
+ druidMeta,
+ new DruidNode("dummy", "dummy", false, 1, null, true, false),
+ new AvaticaMonitor()
+ );
+ }
+
+ @Test
+ public void testNonPostRequestReturns405() throws Exception
+ {
+ DruidMeta druidMeta = EasyMock.mock(DruidMeta.class);
+ DruidAvaticaProtobufHandler handler = new DruidAvaticaProtobufHandler(
+ druidMeta,
+ new DruidNode("dummy", "dummy", false, 1, null, true, false),
+ new AvaticaMonitor()
+ );
+
+ Request request = EasyMock.mock(Request.class);
+ Response response = EasyMock.mock(Response.class);
+ Callback callback = EasyMock.mock(Callback.class);
+ HttpURI httpURI = EasyMock.mock(HttpURI.class);
+
+ EasyMock.expect(request.getHttpURI()).andReturn(httpURI);
+ EasyMock.expect(httpURI.getPath()).andReturn(DruidAvaticaProtobufHandler.AVATICA_PATH_NO_TRAILING_SLASH);
+ EasyMock.expect(request.getMethod()).andReturn("GET");
+
+ response.setStatus(405);
+ EasyMock.expectLastCall();
+
+ response.write(
+ EasyMock.eq(true),
+ EasyMock.anyObject(ByteBuffer.class),
+ EasyMock.eq(callback)
+ );
+ EasyMock.expectLastCall();
+
+ EasyMock.replay(request, response, callback, httpURI);
+
+ boolean handled = handler.handle(request, response, callback);
+
+ Assertions.assertTrue(handled, "Handler should have handled the request");
+ EasyMock.verify(request, response, callback, httpURI);
+ }
+}
diff --git a/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaProtobufHandlerTest.java b/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaProtobufHandlerTest.java
index bbcb0dba9286..decf79ed2e0a 100644
--- a/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaProtobufHandlerTest.java
+++ b/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaProtobufHandlerTest.java
@@ -19,9 +19,17 @@
package org.apache.druid.sql.avatica;
-import org.apache.calcite.avatica.server.AbstractAvaticaHandler;
import org.apache.druid.java.util.common.StringUtils;
import org.apache.druid.server.DruidNode;
+import org.easymock.EasyMock;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.Callback;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.nio.ByteBuffer;
public class DruidAvaticaProtobufHandlerTest extends DruidAvaticaHandlerTest
{
@@ -35,7 +43,7 @@ protected String getJdbcUrlTail()
}
@Override
- protected AbstractAvaticaHandler getAvaticaHandler(final DruidMeta druidMeta)
+ protected DruidAvaticaProtobufHandler getAvaticaHandler(final DruidMeta druidMeta)
{
return new DruidAvaticaProtobufHandler(
druidMeta,
@@ -43,4 +51,41 @@ protected AbstractAvaticaHandler getAvaticaHandler(final DruidMeta druidMeta)
new AvaticaMonitor()
);
}
+
+ @Test
+ public void testNonPostRequestReturns405() throws Exception
+ {
+ DruidMeta druidMeta = EasyMock.mock(DruidMeta.class);
+ DruidAvaticaProtobufHandler handler = new DruidAvaticaProtobufHandler(
+ druidMeta,
+ new DruidNode("dummy", "dummy", false, 1, null, true, false),
+ new AvaticaMonitor()
+ );
+
+ Request request = EasyMock.mock(Request.class);
+ Response response = EasyMock.mock(Response.class);
+ Callback callback = EasyMock.mock(Callback.class);
+ HttpURI httpURI = EasyMock.mock(HttpURI.class);
+
+ EasyMock.expect(request.getHttpURI()).andReturn(httpURI);
+ EasyMock.expect(httpURI.getPath()).andReturn(DruidAvaticaProtobufHandler.AVATICA_PATH_NO_TRAILING_SLASH);
+ EasyMock.expect(request.getMethod()).andReturn("GET");
+
+ response.setStatus(405);
+ EasyMock.expectLastCall();
+
+ response.write(
+ EasyMock.eq(true),
+ EasyMock.anyObject(ByteBuffer.class),
+ EasyMock.eq(callback)
+ );
+ EasyMock.expectLastCall();
+
+ EasyMock.replay(request, response, callback, httpURI);
+
+ boolean handled = handler.handle(request, response, callback);
+
+ Assertions.assertTrue(handled, "Handler should have handled the request");
+ EasyMock.verify(request, response, callback, httpURI);
+ }
}
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 4f0d53dfd027..799cf1cc79f5 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
@@ -437,15 +437,7 @@ public void testCountStarWithMissingIntervalsContext() throws Exception
final MockHttpServletResponse response = postForAsyncResponse(sqlQuery, makeRegularUserReq());
- // In tests, MockHttpServletResponse stores headers as a MultiMap.
- // This allows the same header key to be set multiple times (e.g., once at the start and once at the end of query processing).
- // As a result, we observe duplicate context entries for this test in the expected set.
- // This differs from typical behavior for other headers, where a new value would overwrite any previously set value.
final Object expectedMissingHeaders = ImmutableList.of(
- ImmutableMap.of(
- "uncoveredIntervals", "2030-01-01/78149827981274-01-01",
- "uncoveredIntervalsOverflowed", "true"
- ),
ImmutableMap.of(
"uncoveredIntervals", "2030-01-01/78149827981274-01-01",
"uncoveredIntervalsOverflowed", "true"