Skip to content

Commit 74a2766

Browse files
authored
Onboard PPL dedup to the analytics-engine path (ROW_NUMBER window function) (opensearch-project#21622)
Signed-off-by: Jialiang Liang <jiallian@amazon.com>
1 parent c625fc6 commit 74a2766

3 files changed

Lines changed: 35 additions & 16 deletions

File tree

sandbox/libs/analytics-framework/src/main/java/org/opensearch/analytics/spi/WindowFunction.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,9 @@
1212

1313
/**
1414
* Window functions a backend may support. Covers aggregate-as-window
15-
* (SUM/AVG/COUNT/MIN/MAX over a frame) — these are what PPL {@code eventstats} lowers
16-
* to. PARTITION BY is not currently supported by the planner. Ranking functions
17-
* (ROW_NUMBER / RANK / DENSE_RANK) are not yet reachable on this route since
18-
* {@code streamstats} (which lowers to them) isn't wired here.
15+
* (SUM/AVG/COUNT/MIN/MAX over a frame) — these are what PPL {@code eventstats}
16+
* lowers to — plus ranking functions (ROW_NUMBER) used by PPL {@code dedup}
17+
* lowering (ROW_NUMBER OVER PARTITION BY ... &lt;= N).
1918
*
2019
* @opensearch.internal
2120
*/
@@ -24,7 +23,9 @@ public enum WindowFunction {
2423
AVG(SqlKind.AVG),
2524
COUNT(SqlKind.COUNT),
2625
MIN(SqlKind.MIN),
27-
MAX(SqlKind.MAX);
26+
MAX(SqlKind.MAX),
27+
/** Sequence number per window partition — backs PPL dedup's row-number filter. */
28+
ROW_NUMBER(SqlKind.ROW_NUMBER);
2829

2930
private final SqlKind sqlKind;
3031

sandbox/plugins/analytics-backend-datafusion/src/main/java/org/opensearch/be/datafusion/DataFusionAnalyticsBackendPlugin.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,19 @@ public Set<JoinCapability> joinCapabilities() {
388388
public Set<WindowCapability> windowCapabilities() {
389389
return Set.of(
390390
new WindowCapability(
391-
Set.of(WindowFunction.SUM, WindowFunction.AVG, WindowFunction.COUNT, WindowFunction.MIN, WindowFunction.MAX),
391+
Set.of(
392+
WindowFunction.SUM,
393+
WindowFunction.AVG,
394+
WindowFunction.COUNT,
395+
WindowFunction.MIN,
396+
WindowFunction.MAX,
397+
// ROW_NUMBER backs PPL `dedup` lowering (ROW_NUMBER OVER PARTITION BY ... <= N).
398+
// isthmus's RexExpressionConverter.visitOver serializes the RexOver inline as a
399+
// Substrait WindowFunctionInvocation; DataFusion's substrait consumer splits it
400+
// into a dedicated LogicalPlan::Window. No adapter or Rust UDF is needed —
401+
// row_number is a Substrait-stdlib window function and a DataFusion built-in.
402+
WindowFunction.ROW_NUMBER
403+
),
392404
Set.copyOf(plugin.getSupportedFormats())
393405
)
394406
);

sandbox/plugins/analytics-engine/src/main/java/org/opensearch/analytics/planner/rules/OpenSearchProjectRule.java

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,10 @@ public void onMatch(RelOptRuleCall call) {
8585
: childViableBackends;
8686

8787
// Window functions (RexOver): PPL `eventstats` / `appendcol` emit window calls inline
88-
// on the projection. Narrow viable backends to those whose WindowCapability declares
89-
// every required function. PARTITION BY is rejected up front — no shuffle exchange
90-
// exists today.
88+
// on the projection; PPL `dedup` lowers to ROW_NUMBER OVER (PARTITION BY ...). Narrow
89+
// viable backends to those whose WindowCapability declares every required function.
90+
// PARTITION BY is rejected for aggregate-as-window functions (no shuffle exchange
91+
// available yet) but allowed for ROW_NUMBER since its partition is local.
9192
Set<WindowFunction> requiredWindowFns = collectWindowFunctions(project.getProjects());
9293
if (!requiredWindowFns.isEmpty()) {
9394
viableBackends = narrowByWindowCapability(viableBackends, requiredWindowFns);
@@ -284,23 +285,28 @@ private boolean isOpaqueOperation(String funcName) {
284285
}
285286

286287
/** Walks project expressions and collects the {@link WindowFunction}s used by any {@link RexOver}.
287-
* PARTITION BY is rejected — no shuffle exchange exists today. Unrecognized window SqlKinds
288-
* (LAG, LEAD, NTILE, etc.) also fail here. */
288+
* PARTITION BY is rejected for aggregate-as-window functions (SUM/AVG/COUNT/MIN/MAX) since
289+
* shuffle exchange isn't wired yet — those run frame-only across all rows on a single fragment.
290+
* ROW_NUMBER is the exception: PPL {@code dedup} lowers to ROW_NUMBER OVER (PARTITION BY ...)
291+
* whose semantics are local to each partition; isthmus + DataFusion's substrait consumer
292+
* emit a Window rel that executes the partition without requiring shuffle (the partition
293+
* is the row-group boundary, not a data-redistribution operator). Unrecognized window
294+
* SqlKinds (LAG, LEAD, NTILE, etc.) also fail here. */
289295
private static Set<WindowFunction> collectWindowFunctions(List<? extends RexNode> exprs) {
290296
Set<WindowFunction> fns = new LinkedHashSet<>();
291297
for (RexNode expr : exprs) {
292298
expr.accept(new org.apache.calcite.rex.RexShuttle() {
293299
@Override
294300
public RexNode visitOver(RexOver over) {
295-
if (!over.getWindow().partitionKeys.isEmpty()) {
296-
throw new IllegalStateException(
297-
"Window OVER (PARTITION BY ...) is not supported — no shuffle exchange available yet"
298-
);
299-
}
300301
WindowFunction fn = WindowFunction.fromSqlKind(over.getAggOperator().getKind());
301302
if (fn == null) {
302303
throw new IllegalStateException("Window function [" + over.getAggOperator().getName() + "] is not supported");
303304
}
305+
if (fn != WindowFunction.ROW_NUMBER && !over.getWindow().partitionKeys.isEmpty()) {
306+
throw new IllegalStateException(
307+
"Window OVER (PARTITION BY ...) is not supported for [" + fn + "] — no shuffle exchange available yet"
308+
);
309+
}
304310
fns.add(fn);
305311
return super.visitOver(over);
306312
}

0 commit comments

Comments
 (0)