What is the bug?
plugins.ppl.values.max.limit cannot currently be honored on the Analytics Engine (composite/parquet) route. The cap is silently ignored today, and simply making the setting reach the planner is not enough — the resulting plan fails to bind on the DataFusion backend and the query returns HTTP 500.
Root cause
The cap is applied at AST-build time by attaching a limit argument to the aggregate:
ppl/src/main/java/org/opensearch/sql/ppl/parser/AstExpressionBuilder.java — visitValuesAggFunctionCall:
int limit = 0; // Default to unlimited
if (astBuilder.getSettings() != null) {
Integer settingValue = astBuilder.getSettings().getSettingValue(Key.PPL_VALUES_MAX_LIMIT);
if (settingValue != null) { limit = settingValue; }
}
if (limit > 0) {
builder.add(new UnresolvedArgument("limit", AstDSL.intLiteral(limit)));
}
That extra argument lowers values(x) to array_agg(DISTINCT x, limit). The analytics-engine backend has no binding for the two-argument form.
Two things stack here:
- On the AE route the setting never reaches the planner at all (it is not seeded into
UnifiedQueryContext.Builder and was not forwarded by RestUnifiedQueryAction), so limit stays 0 and no argument is attached — the cap is silently ignored.
- Even once the setting does reach the planner, the backend cannot execute the result.
So the cause recorded in Capability.VALUES_LIMIT_NOT_HONORED ("the aggregate rewriter emits no limit") is accurate in effect, and (2) is the blocking half.
How can one reproduce the bug?
Verified against a live single-node cluster with composite-engine, parquet-data-format, analytics-engine, analytics-backend-datafusion, analytics-backend-lucene and the SQL plugin.
# parquet-backed composite index, auto-generated doc ids
curl -XPUT localhost:9200/ae_probe -H 'Content-Type: application/json' -d '{
"settings":{"index":{"number_of_shards":1,"pluggable.dataformat.enabled":true,
"pluggable.dataformat":"composite","composite.primary_data_format":"parquet",
"composite.secondary_data_formats":["lucene"]}},
"mappings":{"properties":{"name":{"type":"keyword"},"age":{"type":"integer"}}}}'
# bulk-load 6 docs name=n1..n6, then:
curl -XPUT localhost:9200/_cluster/settings -H 'Content-Type: application/json' \
-d '{"transient":{"plugins.ppl.values.max.limit":3}}'
curl -XPOST localhost:9200/_plugins/_ppl -H 'Content-Type: application/json' \
-d '{"query":"source=ae_probe | stats values(name) as v"}'
Current behavior — the cap is ignored, all 6 values returned:
6 values: ['n1','n2','n3','n4','n5','n6']
With the setting forwarded into the unified query context (i.e. the naive fix), the same query fails:
{"error":{"reason":"There was internal problem at backend",
"details":"Internal error [task_id=63]","type":"RuntimeException"},"status":500}
with, in the node log:
[ERROR][o.o.a.e.DefaultPlanExecutor] [analytics-engine] internal error [task_id=63]
java.lang.UnsupportedOperationException: Unable to find binding for call array_agg(DISTINCT $0, $1)
What is the expected behavior?
values() / list() should honor plugins.ppl.values.max.limit on the analytics-engine route, as they do on the default engine.
Fixing this requires backend support for the limited aggregate form (a binding for array_agg(DISTINCT x, n), or an equivalent sort+limit rewrite in PplAggregateCallRewriter) — it cannot be fixed by settings plumbing alone. Once that lands, Key.PPL_VALUES_MAX_LIMIT can be added to RestUnifiedQueryAction.FORWARDED_CLUSTER_SETTINGS and Capability.VALUES_LIMIT_NOT_HONORED (with the @RequiresCapability on CalciteMultiValueStatsIT.testValuesFunctionRespectsConfiguredLimit) removed.
Do you have any additional context?
Found while fixing settings-forwarding gaps on the AE path in #5611. That PR deliberately excludes this key for the reason above — forwarding it would convert a silently-ignored cap into a hard 500 — and documents the exclusion in code so it is not "helpfully" added later.
Related: #5734, #5735.
What is the bug?
plugins.ppl.values.max.limitcannot currently be honored on the Analytics Engine (composite/parquet) route. The cap is silently ignored today, and simply making the setting reach the planner is not enough — the resulting plan fails to bind on the DataFusion backend and the query returns HTTP 500.Root cause
The cap is applied at AST-build time by attaching a
limitargument to the aggregate:ppl/src/main/java/org/opensearch/sql/ppl/parser/AstExpressionBuilder.java—visitValuesAggFunctionCall:That extra argument lowers
values(x)toarray_agg(DISTINCT x, limit). The analytics-engine backend has no binding for the two-argument form.Two things stack here:
UnifiedQueryContext.Builderand was not forwarded byRestUnifiedQueryAction), solimitstays0and no argument is attached — the cap is silently ignored.So the cause recorded in
Capability.VALUES_LIMIT_NOT_HONORED("the aggregate rewriter emits no limit") is accurate in effect, and (2) is the blocking half.How can one reproduce the bug?
Verified against a live single-node cluster with
composite-engine,parquet-data-format,analytics-engine,analytics-backend-datafusion,analytics-backend-luceneand the SQL plugin.Current behavior — the cap is ignored, all 6 values returned:
With the setting forwarded into the unified query context (i.e. the naive fix), the same query fails:
with, in the node log:
What is the expected behavior?
values()/list()should honorplugins.ppl.values.max.limiton the analytics-engine route, as they do on the default engine.Fixing this requires backend support for the limited aggregate form (a binding for
array_agg(DISTINCT x, n), or an equivalent sort+limit rewrite inPplAggregateCallRewriter) — it cannot be fixed by settings plumbing alone. Once that lands,Key.PPL_VALUES_MAX_LIMITcan be added toRestUnifiedQueryAction.FORWARDED_CLUSTER_SETTINGSandCapability.VALUES_LIMIT_NOT_HONORED(with the@RequiresCapabilityonCalciteMultiValueStatsIT.testValuesFunctionRespectsConfiguredLimit) removed.Do you have any additional context?
Found while fixing settings-forwarding gaps on the AE path in #5611. That PR deliberately excludes this key for the reason above — forwarding it would convert a silently-ignored cap into a hard 500 — and documents the exclusion in code so it is not "helpfully" added later.
Related: #5734, #5735.