This is an automated email from the ASF dual-hosted git repository.

yashmayya pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new f216ed25ac2 Add tests for chaining colocated joins, window functions 
and set operations without shuffle (#18997)
f216ed25ac2 is described below

commit f216ed25ac2319fd3654febe062c9b097b2f74b3
Author: Yash Mayya <[email protected]>
AuthorDate: Fri Jul 17 11:47:51 2026 -0700

    Add tests for chaining colocated joins, window functions and set operations 
without shuffle (#18997)
---
 .../apache/pinot/query/QueryCompilationTest.java   | 78 ++++++++++++++++++++++
 .../src/test/resources/queries/QueryHints.json     |  8 +++
 2 files changed, 86 insertions(+)

diff --git 
a/pinot-query-planner/src/test/java/org/apache/pinot/query/QueryCompilationTest.java
 
b/pinot-query-planner/src/test/java/org/apache/pinot/query/QueryCompilationTest.java
index 123bb5cafb3..715cd421774 100644
--- 
a/pinot-query-planner/src/test/java/org/apache/pinot/query/QueryCompilationTest.java
+++ 
b/pinot-query-planner/src/test/java/org/apache/pinot/query/QueryCompilationTest.java
@@ -1161,6 +1161,84 @@ public class QueryCompilationTest extends 
QueryEnvironmentTestBase {
     return sendNodes;
   }
 
+  /// When colocation hints are applied to a chain of operations that are all 
keyed on the same (partition) column, the
+  /// whole chain executes without a data shuffle: every hash-distributed 
exchange in the plan is pre-partitioned. This
+  /// covers combinations of colocated joins, window functions and set 
operations (see {@link #colocatedChains}). The
+  /// join key is {@code a.col2}/{@code b.col1} (each table's partition 
column), so the join is colocated, and the
+  /// window and set op keep that same key.
+  @Test(dataProvider = "colocatedChains")
+  public void testColocatedOperationsChainWithoutShuffle(String description, 
String query) {
+    List<MailboxSendNode> hashSends = 
findHashSendNodes(_queryEnvironment.planQuery(query));
+    assertFalse(hashSends.isEmpty(), "Expected at least one hash exchange in 
the chain: " + description);
+    for (MailboxSendNode sendNode : hashSends) {
+      assertTrue(sendNode.isPrePartitioned(),
+          "Colocated chain '" + description + "' should have no shuffle, but a 
hash exchange was not pre-partitioned");
+    }
+  }
+
+  @DataProvider(name = "colocatedChains")
+  private Object[][] colocatedChains() {
+    // joinOptions + windowOptions together (attach to the join and window in 
a single SELECT).
+    String joinAndWindowHint = "/*+ 
joinOptions(is_colocated_by_join_keys='true'), "
+        + "windowOptions(is_partitioned_by_window_keys='true') */";
+    String joinHint = "/*+ joinOptions(is_colocated_by_join_keys='true') */";
+    String windowHint = "/*+ 
windowOptions(is_partitioned_by_window_keys='true') */";
+    String setOpHint = "/*+ setOpOptions(is_colocated_by_set_op_keys='true') 
*/";
+    String join = "a JOIN b ON a.col2 = b.col1";
+    return new Object[][]{
+        {"join -> window",
+            "SELECT " + joinAndWindowHint + " a.col2, SUM(a.col3) OVER 
(PARTITION BY a.col2) FROM " + join},
+        {"set op of colocated joins", "SELECT " + setOpHint + " * FROM (SELECT 
" + joinHint + " a.col2 FROM " + join
+            + " UNION ALL SELECT " + joinHint + " a.col2 FROM " + join + ")"},
+        {"set op of colocated windows", "SELECT " + setOpHint + " * FROM 
(SELECT " + windowHint
+            + " col2, SUM(col3) OVER (PARTITION BY col2) FROM a UNION ALL 
SELECT " + windowHint
+            + " col2, SUM(col3) OVER (PARTITION BY col2) FROM a)"},
+        {"join -> window -> set op", "SELECT " + setOpHint + " * FROM (SELECT 
" + joinAndWindowHint
+            + " a.col2, SUM(a.col3) OVER (PARTITION BY a.col2) FROM " + join + 
" UNION ALL SELECT " + joinAndWindowHint
+            + " a.col2, SUM(a.col3) OVER (PARTITION BY a.col2) FROM " + join + 
")"},
+    };
+  }
+
+  /// Baseline for {@link #testColocatedOperationsChainWithoutShuffle}: 
without the colocation hints the same
+  /// join -> window chain still shuffles (at least one hash exchange is not 
pre-partitioned), proving the hints are
+  /// what eliminate the shuffles.
+  @Test
+  public void testChainWithoutColocationHintsStillShuffles() {
+    String query = "SELECT a.col2, SUM(a.col3) OVER (PARTITION BY a.col2) FROM 
a JOIN b ON a.col2 = b.col1";
+    List<MailboxSendNode> hashSends = 
findHashSendNodes(_queryEnvironment.planQuery(query));
+    assertTrue(hashSends.stream().anyMatch(sendNode -> 
!sendNode.isPrePartitioned()),
+        "Without the colocation hints, the join -> window chain should contain 
at least one shuffled hash exchange");
+  }
+
+  /// Set-op-specific baseline for the "set op of colocated joins" chain: with
+  /// {@code is_colocated_by_set_op_keys='false'} the set-op exchange is 
forced back to a shuffle (while the per-branch
+  /// joins stay colocated), proving the set-op hint is what keeps the set-op 
level of the chain colocated.
+  @Test
+  public void testSetOpChainWithoutSetOpHintShuffles() {
+    String join = "a JOIN b ON a.col2 = b.col1";
+    String joinHint = "/*+ joinOptions(is_colocated_by_join_keys='true') */";
+    String query = "SELECT /*+ 
setOpOptions(is_colocated_by_set_op_keys='false') */ * FROM (SELECT " + joinHint
+        + " a.col2 FROM " + join + " UNION ALL SELECT " + joinHint + " a.col2 
FROM " + join + ")";
+    List<MailboxSendNode> hashSends = 
findHashSendNodes(_queryEnvironment.planQuery(query));
+    assertTrue(hashSends.stream().anyMatch(sendNode -> 
!sendNode.isPrePartitioned()),
+        "With is_colocated_by_set_op_keys='false', the set-op level of the 
chain should shuffle");
+  }
+
+  /// Collects the {@link MailboxSendNode} at the root of every 
hash-distributed stage in the plan (i.e. every
+  /// inter-stage hash exchange). A pre-partitioned send is a direct, 
no-shuffle exchange; a non-pre-partitioned one is
+  /// a full shuffle.
+  private List<MailboxSendNode> findHashSendNodes(DispatchableSubPlan 
dispatchableSubPlan) {
+    List<MailboxSendNode> sendNodes = new ArrayList<>();
+    for (DispatchablePlanFragment fragment : 
dispatchableSubPlan.getQueryStages()) {
+      PlanNode root = fragment.getPlanFragment().getFragmentRoot();
+      if (root instanceof MailboxSendNode
+          && ((MailboxSendNode) root).getDistributionType() == 
RelDistribution.Type.HASH_DISTRIBUTED) {
+        sendNodes.add((MailboxSendNode) root);
+      }
+    }
+    return sendNodes;
+  }
+
   /**
    * Finds the DispatchablePlanFragment containing a JoinNode (non-leaf, 
non-root stage).
    */
diff --git a/pinot-query-runtime/src/test/resources/queries/QueryHints.json 
b/pinot-query-runtime/src/test/resources/queries/QueryHints.json
index c8f872bd3ee..913b24df0ab 100644
--- a/pinot-query-runtime/src/test/resources/queries/QueryHints.json
+++ b/pinot-query-runtime/src/test/resources/queries/QueryHints.json
@@ -183,6 +183,14 @@
       {
         "description": "INTERSECT with is_colocated_by_set_op_keys='false' 
disables auto-detected pre-partitioning and falls back to a shuffle; results 
stay correct",
         "sql": "SELECT /*+ setOpOptions(is_colocated_by_set_op_keys='false') 
*/ {tbl1}.num FROM {tbl1} /*+ tableOptions(partition_function='hashcode', 
partition_key='num', partition_size='4') */ INTERSECT SELECT {tbl2}.num FROM 
{tbl2} /*+ tableOptions(partition_function='hashcode', partition_key='num', 
partition_size='4') */"
+      },
+      {
+        "description": "Chained colocated operations stay correct: a colocated 
JOIN on num feeding a window partitioned by num (the no-shuffle plan shape is 
asserted in QueryCompilationTest)",
+        "sql": "SELECT /*+ joinOptions(is_colocated_by_join_keys='true'), 
windowOptions(is_partitioned_by_window_keys='true') */ {tbl1}.num, COUNT(*) 
OVER (PARTITION BY {tbl1}.num) FROM {tbl1} /*+ 
tableOptions(partition_function='hashcode', partition_key='num', 
partition_size='4') */ JOIN {tbl2} /*+ 
tableOptions(partition_function='hashcode', partition_key='num', 
partition_size='4') */ ON {tbl1}.num = {tbl2}.num"
+      },
+      {
+        "description": "Chained colocated operations stay correct: a UNION ALL 
of two colocated JOINs on num, set-op hint on the outer wrap and join hint per 
branch (the no-shuffle plan shape is asserted in QueryCompilationTest)",
+        "sql": "SELECT /*+ setOpOptions(is_colocated_by_set_op_keys='true') */ 
* FROM (SELECT /*+ joinOptions(is_colocated_by_join_keys='true') */ {tbl1}.num 
FROM {tbl1} /*+ tableOptions(partition_function='hashcode', 
partition_key='num', partition_size='4') */ JOIN {tbl2} /*+ 
tableOptions(partition_function='hashcode', partition_key='num', 
partition_size='4') */ ON {tbl1}.num = {tbl2}.num UNION ALL SELECT /*+ 
joinOptions(is_colocated_by_join_keys='true') */ {tbl1}.num FROM {tbl1} /*+ ta 
[...]
       }
     ]
   },


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to