diff --git a/direct-query/src/main/java/org/opensearch/sql/directquery/transport/model/ExecuteDirectQueryActionResponse.java b/direct-query/src/main/java/org/opensearch/sql/directquery/transport/model/ExecuteDirectQueryActionResponse.java index 0b573e162fc..e5c99c53493 100644 --- a/direct-query/src/main/java/org/opensearch/sql/directquery/transport/model/ExecuteDirectQueryActionResponse.java +++ b/direct-query/src/main/java/org/opensearch/sql/directquery/transport/model/ExecuteDirectQueryActionResponse.java @@ -131,19 +131,11 @@ private Map parseResult( Map parsedResults = new HashMap<>(); try { - // Add type to JSON if it doesn't already have it - final String resultWithType; - if (!rawResult.contains("\"type\":")) { - resultWithType = addTypeFieldToJson(rawResult, dataSourceType); - } else { - resultWithType = rawResult; - } - DataSourceResult result; // Parse based on the determined data source type switch (dataSourceType.toLowerCase()) { case "prometheus": - result = OBJECT_MAPPER.readValue(resultWithType, PrometheusResult.class); + result = OBJECT_MAPPER.readValue(rawResult, PrometheusResult.class); break; // Add cases for other data source types as they're implemented default: @@ -163,15 +155,4 @@ private Map parseResult( return parsedResults; } - - /** - * Adds a type field to the JSON string for proper polymorphic deserialization. - * - * @param rawJson The raw JSON string without a type field - * @param type The type to add - * @return Modified JSON string with type field - */ - private String addTypeFieldToJson(String rawJson, String type) { - return rawJson.replaceFirst("\\{", "{\"type\":\"" + type + "\","); - } } diff --git a/direct-query/src/main/java/org/opensearch/sql/directquery/transport/model/datasource/DataSourceResult.java b/direct-query/src/main/java/org/opensearch/sql/directquery/transport/model/datasource/DataSourceResult.java index b8714623807..6ed77dcf705 100644 --- a/direct-query/src/main/java/org/opensearch/sql/directquery/transport/model/datasource/DataSourceResult.java +++ b/direct-query/src/main/java/org/opensearch/sql/directquery/transport/model/datasource/DataSourceResult.java @@ -5,15 +5,14 @@ package org.opensearch.sql.directquery.transport.model.datasource; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; - /** * * @opensearch.experimental * * Interface for results from various data sources. + * + *

Concrete result types are dispatched by the {@code dataSourceType} string carried alongside + * the serialized payload in the transport protocol (see {@code ExecuteDirectQueryActionResponse}), + * so no in-JSON Jackson type discriminator is used here. */ -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") -@JsonSubTypes({@JsonSubTypes.Type(value = PrometheusResult.class, name = "prometheus")}) public interface DataSourceResult {} diff --git a/direct-query/src/main/java/org/opensearch/sql/directquery/transport/model/datasource/PrometheusResult.java b/direct-query/src/main/java/org/opensearch/sql/directquery/transport/model/datasource/PrometheusResult.java index 09ff30598d8..ba0abd53cc8 100644 --- a/direct-query/src/main/java/org/opensearch/sql/directquery/transport/model/datasource/PrometheusResult.java +++ b/direct-query/src/main/java/org/opensearch/sql/directquery/transport/model/datasource/PrometheusResult.java @@ -7,7 +7,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonTypeName; import java.util.List; import java.util.Map; import lombok.Getter; @@ -21,7 +20,6 @@ */ @Getter @Setter -@JsonTypeName("prometheus") @JsonIgnoreProperties(ignoreUnknown = true) public class PrometheusResult implements DataSourceResult { diff --git a/direct-query/src/test/java/org/opensearch/sql/directquery/transport/model/ExecuteDirectQueryActionResponseTest.java b/direct-query/src/test/java/org/opensearch/sql/directquery/transport/model/ExecuteDirectQueryActionResponseTest.java index 9f4ab5fa9e1..68ae64d40d6 100644 --- a/direct-query/src/test/java/org/opensearch/sql/directquery/transport/model/ExecuteDirectQueryActionResponseTest.java +++ b/direct-query/src/test/java/org/opensearch/sql/directquery/transport/model/ExecuteDirectQueryActionResponseTest.java @@ -15,6 +15,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; import org.opensearch.core.common.io.stream.InputStreamStreamInput; @@ -147,6 +148,43 @@ public void testStreamSerializationWithPrometheusResult() throws IOException { assertInstanceOf(PrometheusResult.class, deserializedResponse.getResults().get("prom-ds-1")); } + @Test + public void testStreamSerializationPreservesTypeMetricLabel() throws IOException { + // Regression for #5684: a metric label literally named "type" must survive the + // writeTo() -> StreamInput round-trip (the transport path), not only parseResult(). + Map results = new HashMap<>(); + PrometheusResult prometheusResult = new PrometheusResult(); + prometheusResult.setResultType("vector"); + PrometheusResult.PrometheusResultItem item = new PrometheusResult.PrometheusResultItem(); + Map metric = new HashMap<>(); + metric.put("type", "counter"); + metric.put("__name__", "http_requests_total"); + item.setMetric(metric); + prometheusResult.setResult(List.of(item)); + results.put("prom-ds-1", prometheusResult); + + ExecuteDirectQueryActionResponse response = + new ExecuteDirectQueryActionResponse("query-type-label", results, "session-type-label"); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + StreamOutput streamOutput = new OutputStreamStreamOutput(outputStream); + response.writeTo(streamOutput); + streamOutput.close(); + + StreamInput streamInput = + new InputStreamStreamInput(new ByteArrayInputStream(outputStream.toByteArray())); + ExecuteDirectQueryActionResponse deserialized = + new ExecuteDirectQueryActionResponse(streamInput); + streamInput.close(); + + PrometheusResult out = (PrometheusResult) deserialized.getResults().get("prom-ds-1"); + assertInstanceOf(PrometheusResult.class, out); + assertEquals("vector", out.getResultType()); + assertEquals(1, out.getResult().size()); + assertEquals("counter", out.getResult().get(0).getMetric().get("type")); + assertEquals("http_requests_total", out.getResult().get(0).getMetric().get("__name__")); + } + @Test public void testStreamSerializationWithNullSessionId() throws IOException { String queryId = "query-no-session"; @@ -287,4 +325,30 @@ public void testConstructorWithPrometheusError() throws IOException { PrometheusResult result = (PrometheusResult) response.getResults().get(dataSourceName); assertNotNull(result); } + + @Test + public void testPrometheusResultWithTypeLabelInMetric() throws IOException { + // Regression test: metrics containing a label named "type" should not cause + // InvalidTypeIdException during deserialization (GitHub #5684) + String queryId = "query-type-label"; + String sessionId = "session-type-label"; + String rawResult = + "{\"resultType\":\"vector\",\"result\":[{\"metric\":" + + "{\"__name__\":\"cpu_usage\",\"type\":\"gauge\",\"instance\":\"localhost:9090\"}," + + "\"value\":[1625000000,\"0.5\"]}]}"; + String dataSourceName = "prom-with-type-label"; + String dataSourceType = "prometheus"; + + ExecuteDirectQueryActionResponse response = + new ExecuteDirectQueryActionResponse( + queryId, rawResult, sessionId, dataSourceName, dataSourceType); + + assertEquals(queryId, response.getQueryId()); + assertEquals(1, response.getResults().size()); + assertInstanceOf(PrometheusResult.class, response.getResults().get(dataSourceName)); + PrometheusResult result = (PrometheusResult) response.getResults().get(dataSourceName); + assertNotNull(result.getResult()); + assertEquals(1, result.getResult().size()); + assertEquals("gauge", result.getResult().get(0).getMetric().get("type")); + } } \ No newline at end of file