Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -131,19 +131,11 @@ private Map<String, DataSourceResult> parseResult(
Map<String, DataSourceResult> 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:
Expand All @@ -163,15 +155,4 @@ private Map<String, DataSourceResult> 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 + "\",");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <p>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 {}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -21,7 +20,6 @@
*/
@Getter
@Setter
@JsonTypeName("prometheus")
@JsonIgnoreProperties(ignoreUnknown = true)
public class PrometheusResult implements DataSourceResult {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String, DataSourceResult> results = new HashMap<>();
PrometheusResult prometheusResult = new PrometheusResult();
prometheusResult.setResultType("vector");
PrometheusResult.PrometheusResultItem item = new PrometheusResult.PrometheusResultItem();
Map<String, String> 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";
Expand Down Expand Up @@ -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"));
}
}
Loading