Skip to content
Merged
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 @@ -107,6 +107,7 @@
import org.opensearch.sql.opensearch.storage.script.CompoundedScriptEngine;
import org.opensearch.sql.plugin.config.EngineExtensionsHolder;
import org.opensearch.sql.plugin.config.OpenSearchPluginModule;
import org.opensearch.sql.plugin.rest.AnalyticsEngineFormatSupport;
import org.opensearch.sql.plugin.rest.AnalyticsExecutorHolder;
import org.opensearch.sql.plugin.rest.RestPPLGrammarAction;
import org.opensearch.sql.plugin.rest.RestPPLQueryAction;
Expand Down Expand Up @@ -271,6 +272,13 @@ public void onFailure(Exception e) {
}
});
} else {
// Analytics route only emits JSON; reject unsupported formats (e.g. csv) with a 4xx.
try {
AnalyticsEngineFormatSupport.validateFormat(sqlRequest.format());
} catch (Exception e) {
RestSqlAction.handleException(channel, e);
return true;
}
unifiedQueryHandler.execute(
sqlRequest.getQuery(),
QueryType.SQL,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.sql.plugin.rest;

import java.util.Locale;
import org.opensearch.sql.common.error.ErrorCode;
import org.opensearch.sql.common.error.ErrorReport;
import org.opensearch.sql.common.error.QueryProcessingStage;
import org.opensearch.sql.protocol.response.format.Format;

/**
* Guards the analytics-engine query route, which only produces JSON. Rejects the alternate output
* formats (csv/raw/viz) the route does not implement, instead of silently answering with JSON.
*/
public final class AnalyticsEngineFormatSupport {

private AnalyticsEngineFormatSupport() {}

/**
* Throw an {@link ErrorReport} if the requested output format is unsupported on the analytics
* engine. JSON/JDBC (the default) is supported; csv/raw/viz are not.
*/
public static void validateFormat(Format format) {
// JDBC (the default) is the JSON contract the analytics route emits.
if (format == Format.JDBC) {
return;
}
throw ErrorReport.wrap(
new IllegalArgumentException(
String.format(
Locale.ROOT,
"response in %s format is not supported on the analytics engine",
format.getFormatName())))
.code(ErrorCode.UNSUPPORTED_OPERATION)
.stage(QueryProcessingStage.ANALYZING)
.location("while selecting the response format for the analytics engine")
.suggestion(
"The analytics engine only returns JSON. Remove the 'format' parameter, or run this"
+ " query against a non-analytics index to use the requested format.")
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.opensearch.sql.opensearch.setting.OpenSearchSettings;
import org.opensearch.sql.plugin.config.EngineExtensionsHolder;
import org.opensearch.sql.plugin.config.OpenSearchPluginModule;
import org.opensearch.sql.plugin.rest.AnalyticsEngineFormatSupport;
import org.opensearch.sql.plugin.rest.AnalyticsExecutorHolder;
import org.opensearch.sql.plugin.rest.RestUnifiedQueryAction;
import org.opensearch.sql.ppl.PPLService;
Expand Down Expand Up @@ -185,6 +186,13 @@ protected void doExecute(
task,
createExplainResponseListener(transformedRequest, clearingListener));
} else {
// Analytics route only emits JSON; reject unsupported formats (e.g. csv) with a 4xx.
try {
AnalyticsEngineFormatSupport.validateFormat(format(transformedRequest));
} catch (Exception e) {
clearingListener.onFailure(e);
return;
}
unifiedQueryHandler.execute(
transformedRequest.getRequest(),
QueryType.PPL,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.sql.plugin.rest;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import org.junit.Test;
import org.opensearch.sql.common.error.ErrorCode;
import org.opensearch.sql.common.error.ErrorReport;
import org.opensearch.sql.protocol.response.format.Format;

/**
* Verifies {@link AnalyticsEngineFormatSupport#validateFormat(Format)} accepts the JSON/JDBC
* default the analytics route emits and rejects unsupported output formats (csv/raw/viz) with a
* structured {@link ErrorReport} so the REST layer returns a clean 4xx instead of silently
* returning JSON.
*/
public class AnalyticsEngineFormatSupportTest {

@Test
public void jdbcFormatIsAccepted() {
// No exception expected — JDBC is the JSON contract the analytics route emits.
AnalyticsEngineFormatSupport.validateFormat(Format.JDBC);
}

@Test
public void csvFormatIsRejectedAsUnsupportedOperation() {
ErrorReport report =
assertThrows(() -> AnalyticsEngineFormatSupport.validateFormat(Format.CSV));
assertEquals(ErrorCode.UNSUPPORTED_OPERATION, report.getCode());
assertTrue(report.getMessage().toLowerCase().contains("csv"));
assertTrue(report.getMessage().contains("analytics engine"));
assertNotNull(report.getSuggestion());
}

@Test
public void rawFormatIsRejected() {
ErrorReport report =
assertThrows(() -> AnalyticsEngineFormatSupport.validateFormat(Format.RAW));
assertEquals(ErrorCode.UNSUPPORTED_OPERATION, report.getCode());
assertTrue(report.getMessage().toLowerCase().contains("raw"));
}

@Test
public void vizFormatIsRejected() {
ErrorReport report =
assertThrows(() -> AnalyticsEngineFormatSupport.validateFormat(Format.VIZ));
assertEquals(ErrorCode.UNSUPPORTED_OPERATION, report.getCode());
}

private static ErrorReport assertThrows(Runnable runnable) {
try {
runnable.run();
} catch (ErrorReport e) {
return e;
}
throw new AssertionError("Expected ErrorReport to be thrown, but nothing was thrown");
}
}
Loading