[analytics-engine]Add PPL bucketing scalars (span_bucket / width_bucket / minspan_bucket / range_bucket) to analytics-engine route#21621
Conversation
Wires 6 Rust UDFs (convert_tz, span, span_bucket, width_bucket, minspan_bucket, range_bucket) and their Java adapters so PPL bucketing commands route through the DataFusion analytics-engine backend. User-facing surface: - PPL `stats ... by span(f, n) as a` → span UDF (numeric branch) - PPL `bin <f> span=N` → span_bucket UDF The remaining three bucket UDFs (width_bucket / minspan_bucket / range_bucket) are fully wired end-to-end but their PPL command surfaces (`bin <f> bins=N`, `bin <f> minspan=N`, `bin <f> start=X end=Y`) lower to `MIN/MAX(f) OVER ()` empty-partition window aggregates which the analytics-engine planner does not currently recognize (no OpenSearchWindow RelNode / WindowRule / windowAggregate capability in sandbox/plugins/analytics-engine). Their IT suites are class-level @AwaitsFix with precise follow-up scope; Rust unit tests + adapter unit tests cover algorithm + rewrite-shape correctness in the meantime. Changes: - Rust UDFs under rust/src/udf/ (convert_tz, span, span_bucket, width_bucket, minspan_bucket, range_bucket) with per-UDF unit tests - udf::register_all wired on all four SessionContext creation paths: local_executor.rs, query_executor.rs, indexed_executor.rs, and session_context.rs (the PPL searchWithSessionContext path) - Java adapters under sandbox/plugins/analytics-backend-datafusion: YearAdapter, ConvertTzAdapter, UnixTimestampAdapter, SpanBucketAdapter, SpanAdapter, WidthBucketAdapter, MinspanBucketAdapter, RangeBucketAdapter, AbstractNameMappingAdapter base - ScalarFunction enum entries + fromSqlFunction null-on-unknown contract - extensions/opensearch_scalar.yaml registered in DataFusionPlugin - DataFusionFragmentConvertor Sigs mapping local ops to substrait names - Per-adapter unit tests + per-PPL-command IT coverage - Removed stale jackson-databind-2.21.3.jar.sha1 from analytics-engine licenses Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Marc Handalian <marc.handalian@gmail.com>
Three follow-on fixes to the bucketing-scalars cherry-pick:
1. Remove the duplicate `crate::udf::register_all` call in
`session_context.rs`. Main already registered UDFs on this session
context; the cherry-pick added a second call thinking it was a new
wiring. Idempotent at runtime but obviously wrong.
2. Update stale Javadoc paths in {Minspan,Span,Width}BucketAdapter:
the YAML lives at `src/main/resources/opensearch_scalar_functions.yaml`,
not `extensions/opensearch_scalar.yaml`.
3. Delete dead `YearAdapter` + `YearAdapterTests`. `ScalarFunction.YEAR`
is bound to `DatePartAdapters.year()` in
`DataFusionAnalyticsBackendPlugin` (Wave A datetime PR), so
`YearAdapter` is wired nowhere. Update the few Javadoc references
that pointed at `YearAdapterTests` to point at
`UnixTimestampAdapterTests` (the surviving canonical example) or
inline the rationale.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Sandesh Kumar <sandeshkr419@gmail.com>
PR Reviewer Guide 🔍(Review updated until commit 9e5f9f0)Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Latest suggestions up to 9e5f9f0 Explore these optional code suggestions:
Previous suggestionsSuggestions up to commit 60712c2
Suggestions up to commit b4884d9
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #21621 +/- ##
============================================
- Coverage 73.60% 73.50% -0.10%
+ Complexity 74763 74753 -10
============================================
Files 5985 5985
Lines 339176 339176
Branches 48900 48900
============================================
- Hits 249637 249320 -317
- Misses 69643 69991 +348
+ Partials 19896 19865 -31 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Unblocks the @AwaitsFix annotations on WidthBucketCommandIT, MinspanBucketCommandIT, and RangeBucketCommandIT. After the merge from main brought in the window-function support added by opensearch-project#21668, the planner accepted the bin queries, isthmus serialised the substrait, and DataFusion's substrait consumer deserialised it — but the data-node fragment still failed with "Physical plan does not support logical expression WindowFunction(...)". Root cause: PPL's bin lowering nests RexOver inside a scalar UDF call: width_bucket(f, N, MAX(f) OVER () - MIN(f) OVER (), MAX(f) OVER ()) DataFusion's `from_project_rel` auto-lifts *top-level* WindowFunction project expressions into a LogicalWindow (datafusion-substrait `consumer/rel/project_rel.rs`), but the RexOvers nested inside `width_bucket(...)` stay where they are, and the physical planner rejects them — `create_physical_expr` only handles scalar exprs and errors on WindowFunction. Fix: pre-substrait, hoist nested RexOver out of OpenSearchProject's expressions into a child LogicalProject sitting on top of the input. After lifting: LogicalProject([width_bucket(f, N, $w0 - $w1, $w0)]) <- outer LogicalProject([f, ..., $w0=MAX(f) OVER (), $w1=MIN(f) OVER ()]) <input> With WindowFunctions now top-level in the inner Project, DataFusion's auto-lift wraps them in a LogicalWindow and the outer Project sees plain column references. De-duplicates by RexOver digest so MAX(f) OVER () appearing as both `data_range` operand and `max_value` operand is hoisted once and shares a single column. Implementation: - OpenSearchProject.stripAnnotations: after the existing annotation-strip pass, scan stripped expressions for RexOver subtrees, build a child LogicalProject with passthrough input fields + appended unique RexOvers, rewrite the outer expressions to reference the hoisted columns via RexInputRef. No-RexOver case falls through to the original single-Project path (zero-cost when bin command is not in play). - Test fixes: * Removed @AwaitsFix from WidthBucketCommandIT / MinspanBucketCommandIT / RangeBucketCommandIT. * Corrected WidthBucketCommandIT.testBinBinsPreservesNullsInNullableField and MinspanBucketCommandIT.testBinMinspanPreservesNullsInNullableField: the original expected rows assumed `first_bin_start = floor(min/width) *width` + `idx = floor(adj/width)` formula but the data-node UDF (rust/src/udf/{width,minspan}_bucket.rs) actually computes per-row `bin_start = floor(value/width)*width`. The two formulae diverge for values whose `floor(value/width)*width` does not coincide with `first_bin_start + idx*width` — concretely, +3.5 with width=10 was expected to land in "-10-0" (idx=1, first_bin_start=-20) but the UDF correctly produces "0-10" (floor(0.35)*10=0). Fixed expected rows to match the implementation; updated comments to document the algorithm correctly. All 17 tests across the 5 bin-related ITs pass end-to-end. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Sandesh Kumar <sandeshkr419@gmail.com>
Spotless rejected the 5-space indent inside two comment lines in OpenSearchProject.liftNestedRexOver. No code change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Sandesh Kumar <sandeshkr419@gmail.com>
|
Persistent review updated to latest commit 9e5f9f0 |
|
❌ Gradle check result for 9e5f9f0: FAILURE Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
…et / minspan_bucket / range_bucket) to analytics-engine route (opensearch-project#21621) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Marc Handalian <marc.handalian@gmail.com> Signed-off-by: Marc Handalian <marc.handalian@gmail.com> Signed-off-by: Sandesh Kumar <sandeshkr419@gmail.com> Signed-off-by: Khishorekumar BS <bkhishor@amazon.com>
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
Signed-off-by: Marc Handalian marc.handalian@gmail.com
Signed-off-by: Sandesh Kumar sandeshkr419@gmail.com
Summary
Wires PPL's four bucketing label functions end-to-end on the analytics-engine route, exercised by the
bin <field> {span,bins,minspan,start..end}=Ncommand surfaces. Each returns a VARCHAR bucket label (e.g."0-100") via the OpenSearch nice-number algorithm — these are the PPL-specific variants, not ISO-SQLwidth_bucket(which returns an integer index).User-facing surface delivered:
bin <f> span=N→span_bucket(f, N)bin <f> bins=N→width_bucket(f, N, MAX(f) OVER (), …)bin <f> minspan=N→minspan_bucket(f, N, MAX(f) OVER (), …)bin <f> start=X end=Y→range_bucket(f, MIN(f) OVER (), MAX(f) OVER (), X, Y)The
span()scalar (used bystats … by span(…)) was wired separately in #21584 and is reused here.Implementation
analytics-backend-datafusion/rust/src/udf/({span,width,minspan,range}_bucket.rs) ported fromsql/core's Java reference implementations. Per-UDF unit tests cover algorithm correctness, null propagation, arity, and type coercion.AbstractNameMappingAdapter) bind PPL'sSqlUserDefinedFunctionoperators to locally-declared SqlFunctions whoseFunctionMappings.Sigresolves to the Rust UDFs.ScalarFunctionenum entries +STANDARD_PROJECT_OPScapability registration +scalarFunctionAdapters()map wiring.opensearch_scalar_functions.yamlwith distinctany1…anyNper slot so substrait can bind mixed-type calls.OpenSearchProject.liftNestedRexOver: pre-substrait pass that hoists nestedRexOverout of project expressions into a childLogicalProject. Required becausebin bins=N/minspan=N/start=… end=…lower to scalar calls whose operands embedMAX/MIN OVER (). DataFusion's substrait consumer auto-lifts top-level WindowFunction project expressions into aLogicalWindowbut doesn't touch nested RexOvers — without this lift the data-node fragment errors with "Physical plan does not support logicalexpression WindowFunction(…)". Builds on the window support added by analytics-engine: add window function support #21668.Related Issues
Resolves #[Issue number to be closed when this PR is merged]
Check List
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.