Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
5d2ad61
Add non-fatal warning channel to PPL query response
ahkcs Jul 27, 2026
418f45e
Return a partial result instead of exhausting PIT on a mapping conflict
ahkcs Jul 27, 2026
e0407f3
Gate partial results on a warning-capable response format
ahkcs Jul 27, 2026
f553b77
Flatten per-index mappings when partitioning for partial results
ahkcs Jul 27, 2026
b475c5b
Refine partial-result index selection and warning wording
ahkcs Jul 27, 2026
dad3bb3
Truncate the excluded-index list in the partial-result warning
ahkcs Jul 27, 2026
078c949
Fix partial-result warning wording to reflect the pushdown criterion
ahkcs Jul 27, 2026
0419c94
Extract partial-result partitioning into its own class with unit tests
ahkcs Jul 27, 2026
6902c39
Allow a per-request override for partial-result mode
ahkcs Jul 28, 2026
83fd527
Merge remote-tracking branch 'upstream/main' into feature/ppl-partial…
ahkcs Jul 28, 2026
2a3eab8
Simplify partial-result warning to name the excluded indices and the fix
ahkcs Jul 28, 2026
19b6187
Do not resolve response format for explain requests
ahkcs Jul 28, 2026
51220fe
Cover the null-warnings branch in QueryResult to satisfy protocol cov…
ahkcs Jul 28, 2026
a996d24
Address review: rename the partial-result setting and fold the fallba…
ahkcs Jul 29, 2026
3d21944
Reuse the already-fetched index mappings for partial-result partitioning
ahkcs Jul 29, 2026
42b4977
Fix formatting of the renamed partial-result setting key
ahkcs Jul 29, 2026
755031e
Revert the index-mapping reuse optimization: it exposed a merge-mutat…
ahkcs Jul 29, 2026
44e0eaa
Reuse the already-fetched index mappings, and stop the merge mutating…
ahkcs Jul 30, 2026
8a040a2
Clear the per-request partial-result state after each query
ahkcs Jul 30, 2026
cdfab96
Update explain golden files for the changed OpenSearchDataType serial…
ahkcs Jul 30, 2026
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 @@ -47,6 +47,7 @@ public enum Key {

/** Query Settings. */
FIELD_TYPE_TOLERANCE("plugins.query.field_type_tolerance"),
PARTIAL_RESULT_ON_MAPPING_CONFLICT("plugins.query.partial_result.on_mapping_conflict.enabled"),

/** Common Settings for SQL and PPL. */
QUERY_MEMORY_LIMIT("plugins.query.memory_limit"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ public class QueryContext {

private static final String PROFILE_KEY = "profile";

private static final String WARNINGS_SUPPORTED_KEY = "warnings_supported";

private static final String PARTIAL_RESULT_OVERRIDE_KEY = "partial_result_override";

/**
* Generates a random UUID and adds to the {@link ThreadContext} as the request id.
*
Expand Down Expand Up @@ -84,4 +88,48 @@ public static void setProfile(boolean profileEnabled) {
public static boolean isProfileEnabled() {
return Boolean.parseBoolean(ThreadContext.get(PROFILE_KEY));
}

/**
* Record whether the requested response format can surface non-fatal warnings. Features that
* return a knowingly-partial result gate on this so they never silently drop data into a format
* (CSV/RAW) that has no warning channel.
*
* @param supported whether the response format carries a warnings channel
*/
public static void setWarningsSupported(boolean supported) {
ThreadContext.put(WARNINGS_SUPPORTED_KEY, Boolean.toString(supported));
}

/**
* @return true if the response format for the current request can surface warnings. Defaults to
* false when unset, so a caller that never declared support cannot get a silent partial
* result.
*/
public static boolean isWarningsSupported() {
return Boolean.parseBoolean(ThreadContext.get(WARNINGS_SUPPORTED_KEY));
}

/**
* Record a per-request override for partial-result mode. When set, it takes precedence over the
* cluster setting: {@code true} forces partial mode on for this request, {@code false} forces it
* off. A {@code null} value (the default) leaves the decision to the cluster setting.
*
* @param override the per-request preference, or null to defer to the cluster setting
*/
public static void setPartialResultOverride(Boolean override) {
if (override == null) {
ThreadContext.remove(PARTIAL_RESULT_OVERRIDE_KEY);
} else {
ThreadContext.put(PARTIAL_RESULT_OVERRIDE_KEY, Boolean.toString(override));
}
}

/**
* @return the per-request partial-result override, or {@code null} when the request expressed no
* preference (in which case the cluster setting decides).
*/
public static Boolean getPartialResultOverride() {
String value = ThreadContext.get(PARTIAL_RESULT_OVERRIDE_KEY);
return value == null ? null : Boolean.parseBoolean(value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.opensearch.sql.calcite.utils.CalciteToolsHelper.OpenSearchRelBuilder;
import org.opensearch.sql.common.setting.Settings;
import org.opensearch.sql.executor.QueryType;
import org.opensearch.sql.executor.Warning;
import org.opensearch.sql.expression.function.FunctionProperties;

public class CalcitePlanContext {
Expand Down Expand Up @@ -58,6 +59,15 @@ public class CalcitePlanContext {
/** Timewrap series mode: "relative", "short", or "exact". */
public static final ThreadLocal<String> timewrapSeries = new ThreadLocal<>();

/**
* Non-fatal warnings raised during planning (e.g. a partial result over a subset of indices) to
* be attached to the query response by the execution engine. Drained in {@code
* OpenSearchExecutionEngine.buildResultSet} and cleared with the other lifecycle signals so it
* never leaks onto the next query on a pooled worker thread.
*/
private static final ThreadLocal<List<Warning>> pendingWarnings =
ThreadLocal.withInitial(ArrayList::new);

/**
* Thread-local tracking which pool executed this query ("sql-worker" or "sql-complex-worker").
*/
Expand Down Expand Up @@ -250,9 +260,30 @@ public static void clearTimewrapSignals() {
stripNullColumns.set(false);
timewrapUnitName.set(null);
timewrapSeries.set(null);
pendingWarnings.remove();
executionPool.set(null);
}

/** Records a non-fatal warning to be attached to the response for the current query. */
public static void addWarning(Warning warning) {
pendingWarnings.get().add(warning);
}

/**
* Returns and clears the warnings collected for the current query, de-duplicated by value. The
* planner may fire a rule that raises a warning more than once for equivalent plan alternatives,
* so identical warnings are collapsed to one.
*/
public static List<Warning> drainWarnings() {
List<Warning> warnings = pendingWarnings.get();
if (warnings.isEmpty()) {
return List.of();
}
List<Warning> drained = warnings.stream().distinct().toList();
pendingWarnings.remove();
return drained;
}

/**
* Snapshot of all thread-local state in CalcitePlanContext. Used when dispatching queries to the
* complex worker pool — capture state on the caller thread, restore on the worker thread.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ class QueryResponse {
private final Cursor cursor;
@lombok.Setter private QueryProfile profile;
@lombok.Setter private Throwable error;

/** Non-fatal notices attached to a successful result; empty for a plain success. */
@lombok.Setter private List<Warning> warnings = List.of();
}

@Data
Expand Down
34 changes: 34 additions & 0 deletions core/src/main/java/org/opensearch/sql/executor/Warning.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.sql.executor;

import lombok.Data;

/**
* A non-fatal notice attached to an otherwise-successful query response. Carried through the
* response path so consumers can distinguish a correct-but-noteworthy result (e.g. a partial result
* over a subset of indices) from a plain success, without turning it into an error.
*/
@Data
public class Warning {

/**
* The result is complete for the indices it covers but omits one or more indices that could not
* be served (e.g. a mapping conflict that prevents aggregation pushdown). This is a cross-surface
* contract: consumers such as OpenSearch Dashboards branch on this {@code type} value, so it must
* not change without coordinating those consumers.
*/
public static final String TYPE_PARTIAL_RESULT = "PARTIAL_RESULT";

/** Machine-readable category, e.g. {@link #TYPE_PARTIAL_RESULT}. */
private final String type;

/** Short human-readable summary. */
private final String message;

/** Optional longer explanation with the specifics and remedy; may be null. */
private final String detail;
}
Loading
Loading