xiangfu0 commented on code in PR #19862:
URL: https://github.com/apache/pinot/pull/19862#discussion_r4289850325


##########
pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/rules/PinotJoinExchangeNodeInsertRule.java:
##########
@@ -127,6 +127,62 @@ private static PinotLogicalExchange 
createExchangeForLookupJoin(PinotHintOptions
     }
   }
 
+  /// Rejects the pairs of distribution types that cannot produce the join's 
rows, which are otherwise silently
+  /// answered wrong. A pair is workable only if every pair of rows that could 
match meets on one worker, and only if
+  /// each row is emitted by one worker.
+  ///
+  /// Broadcasting a side gives every worker all of its rows, so it stands in 
for any partitioning of the other side.
+  /// Broadcasting both sides gives every worker all the rows of both, and 
each joined row is then emitted once per
+  /// worker. Broadcasting neither leaves both sides partitioned, and they 
only line up when both are partitioned the
+  /// same way on the join keys, which also requires the join to have keys to 
partition on.
+  ///
+  /// An outer join additionally emits the rows of its outer side that matched 
nothing, which a worker can only tell
+  /// once it holds every row of the opposite side. So the broadcast side must 
be the side opposite the outer one, and
+  /// a FULL join, whose two sides are both outer, cannot be answered with a 
broadcast side at all.
+  private static void 
validateDistributionTypes(PinotHintOptions.DistributionType 
leftDistributionType,
+      PinotHintOptions.DistributionType rightDistributionType, JoinInfo 
joinInfo, JoinRelType joinType) {
+    boolean leftBroadcast = leftDistributionType == 
PinotHintOptions.DistributionType.BROADCAST;
+    boolean rightBroadcast = rightDistributionType == 
PinotHintOptions.DistributionType.BROADCAST;
+    if (leftBroadcast && rightBroadcast) {
+      throw new IllegalArgumentException(
+          "Cannot broadcast both sides of the join: every worker would hold 
every row of both sides and emit each "
+              + "joined row, repeating the join result once per worker. 
Broadcast at most one side.");
+    }
+    if (leftBroadcast || rightBroadcast) {
+      switch (joinType) {
+        case LEFT:
+          Preconditions.checkArgument(rightBroadcast,
+              "LEFT join requires the right side to be the broadcast one: a 
worker can only tell that a left row "
+                  + "matched nothing if it holds every right row, got left: 
%s, right: %s", leftDistributionType,
+              rightDistributionType);
+          break;
+        case RIGHT:
+          Preconditions.checkArgument(leftBroadcast,
+              "RIGHT join requires the left side to be the broadcast one: a 
worker can only tell that a right row "
+                  + "matched nothing if it holds every left row, got left: %s, 
right: %s", leftDistributionType,
+              rightDistributionType);
+          break;
+        case FULL:
+          throw new IllegalArgumentException(
+              "FULL join cannot broadcast a side: both sides emit the rows 
that matched nothing, so neither side can "
+                  + "be the partitioned one. Distribute both sides by hash on 
the join keys.");
+        default:
+          break;
+      }
+      return;
+    }
+    Preconditions.checkArgument(!joinInfo.leftKeys.isEmpty(),
+        "Join without equi keys requires one side to be broadcast: with both 
sides partitioned, a pair of rows that "
+            + "could match may land on different workers, got left: %s, right: 
%s", leftDistributionType,
+        rightDistributionType);
+    Preconditions.checkArgument(
+        leftDistributionType == rightDistributionType

Review Comment:
   Minor/back-compat note: a user who was running one of these broken hint 
combinations used to silently get wrong rows, and will now get a hard query 
failure instead. That's the right trade since the old answer was incorrect, but 
it is a behavior change worth a line in the release notes so people know why a 
previously-accepted query now errors.



##########
pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/rules/PinotJoinExchangeNodeInsertRule.java:
##########
@@ -127,6 +127,62 @@ private static PinotLogicalExchange 
createExchangeForLookupJoin(PinotHintOptions
     }
   }
 
+  /// Rejects the pairs of distribution types that cannot produce the join's 
rows, which are otherwise silently
+  /// answered wrong. A pair is workable only if every pair of rows that could 
match meets on one worker, and only if
+  /// each row is emitted by one worker.
+  ///
+  /// Broadcasting a side gives every worker all of its rows, so it stands in 
for any partitioning of the other side.
+  /// Broadcasting both sides gives every worker all the rows of both, and 
each joined row is then emitted once per
+  /// worker. Broadcasting neither leaves both sides partitioned, and they 
only line up when both are partitioned the
+  /// same way on the join keys, which also requires the join to have keys to 
partition on.
+  ///
+  /// An outer join additionally emits the rows of its outer side that matched 
nothing, which a worker can only tell
+  /// once it holds every row of the opposite side. So the broadcast side must 
be the side opposite the outer one, and
+  /// a FULL join, whose two sides are both outer, cannot be answered with a 
broadcast side at all.
+  private static void 
validateDistributionTypes(PinotHintOptions.DistributionType 
leftDistributionType,
+      PinotHintOptions.DistributionType rightDistributionType, JoinInfo 
joinInfo, JoinRelType joinType) {
+    boolean leftBroadcast = leftDistributionType == 
PinotHintOptions.DistributionType.BROADCAST;
+    boolean rightBroadcast = rightDistributionType == 
PinotHintOptions.DistributionType.BROADCAST;
+    if (leftBroadcast && rightBroadcast) {
+      throw new IllegalArgumentException(
+          "Cannot broadcast both sides of the join: every worker would hold 
every row of both sides and emit each "
+              + "joined row, repeating the join result once per worker. 
Broadcast at most one side.");
+    }
+    if (leftBroadcast || rightBroadcast) {
+      switch (joinType) {
+        case LEFT:
+          Preconditions.checkArgument(rightBroadcast,
+              "LEFT join requires the right side to be the broadcast one: a 
worker can only tell that a left row "
+                  + "matched nothing if it holds every right row, got left: 
%s, right: %s", leftDistributionType,
+              rightDistributionType);
+          break;
+        case RIGHT:
+          Preconditions.checkArgument(leftBroadcast,
+              "RIGHT join requires the left side to be the broadcast one: a 
worker can only tell that a right row "
+                  + "matched nothing if it holds every left row, got left: %s, 
right: %s", leftDistributionType,
+              rightDistributionType);
+          break;
+        case FULL:
+          throw new IllegalArgumentException(
+              "FULL join cannot broadcast a side: both sides emit the rows 
that matched nothing, so neither side can "
+                  + "be the partitioned one. Distribute both sides by hash on 
the join keys.");
+        default:
+          break;
+      }
+      return;
+    }
+    Preconditions.checkArgument(!joinInfo.leftKeys.isEmpty(),
+        "Join without equi keys requires one side to be broadcast: with both 
sides partitioned, a pair of rows that "
+            + "could match may land on different workers, got left: %s, right: 
%s", leftDistributionType,
+        rightDistributionType);
+    Preconditions.checkArgument(
+        leftDistributionType == rightDistributionType
+            && leftDistributionType != 
PinotHintOptions.DistributionType.RANDOM,
+        "Both sides of the join must be distributed the same way on the join 
keys when neither side is broadcast, "
+            + "otherwise rows sharing a key land on different workers, got 
left: %s, right: %s", leftDistributionType,

Review Comment:
   Minor: `local`/`local` passes here on trust, without checking the two tables 
are actually co-partitioned or one is replicated. That matches the existing 
colocated-hint philosophy of trusting the user, so no change needed — just 
calling out that this pair is accepted on faith while the others are reasoned 
about.



##########
pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/rules/PinotJoinExchangeNodeInsertRule.java:
##########
@@ -127,6 +127,62 @@ private static PinotLogicalExchange 
createExchangeForLookupJoin(PinotHintOptions
     }
   }
 
+  /// Rejects the pairs of distribution types that cannot produce the join's 
rows, which are otherwise silently
+  /// answered wrong. A pair is workable only if every pair of rows that could 
match meets on one worker, and only if
+  /// each row is emitted by one worker.
+  ///
+  /// Broadcasting a side gives every worker all of its rows, so it stands in 
for any partitioning of the other side.
+  /// Broadcasting both sides gives every worker all the rows of both, and 
each joined row is then emitted once per
+  /// worker. Broadcasting neither leaves both sides partitioned, and they 
only line up when both are partitioned the
+  /// same way on the join keys, which also requires the join to have keys to 
partition on.
+  ///
+  /// An outer join additionally emits the rows of its outer side that matched 
nothing, which a worker can only tell
+  /// once it holds every row of the opposite side. So the broadcast side must 
be the side opposite the outer one, and
+  /// a FULL join, whose two sides are both outer, cannot be answered with a 
broadcast side at all.
+  private static void 
validateDistributionTypes(PinotHintOptions.DistributionType 
leftDistributionType,
+      PinotHintOptions.DistributionType rightDistributionType, JoinInfo 
joinInfo, JoinRelType joinType) {
+    boolean leftBroadcast = leftDistributionType == 
PinotHintOptions.DistributionType.BROADCAST;
+    boolean rightBroadcast = rightDistributionType == 
PinotHintOptions.DistributionType.BROADCAST;
+    if (leftBroadcast && rightBroadcast) {
+      throw new IllegalArgumentException(
+          "Cannot broadcast both sides of the join: every worker would hold 
every row of both sides and emit each "
+              + "joined row, repeating the join result once per worker. 
Broadcast at most one side.");
+    }
+    if (leftBroadcast || rightBroadcast) {
+      switch (joinType) {
+        case LEFT:
+          Preconditions.checkArgument(rightBroadcast,
+              "LEFT join requires the right side to be the broadcast one: a 
worker can only tell that a left row "
+                  + "matched nothing if it holds every right row, got left: 
%s, right: %s", leftDistributionType,
+              rightDistributionType);
+          break;
+        case RIGHT:
+          Preconditions.checkArgument(leftBroadcast,
+              "RIGHT join requires the left side to be the broadcast one: a 
worker can only tell that a right row "
+                  + "matched nothing if it holds every left row, got left: %s, 
right: %s", leftDistributionType,
+              rightDistributionType);
+          break;
+        case FULL:
+          throw new IllegalArgumentException(
+              "FULL join cannot broadcast a side: both sides emit the rows 
that matched nothing, so neither side can "
+                  + "be the partitioned one. Distribute both sides by hash on 
the join keys.");
+        default:
+          break;
+      }
+      return;
+    }
+    Preconditions.checkArgument(!joinInfo.leftKeys.isEmpty(),
+        "Join without equi keys requires one side to be broadcast: with both 
sides partitioned, a pair of rows that "
+            + "could match may land on different workers, got left: %s, right: 
%s", leftDistributionType,
+        rightDistributionType);
+    Preconditions.checkArgument(
+        leftDistributionType == rightDistributionType

Review Comment:
   Minor/back-compat note: a user who was running one of these broken hint 
combinations used to silently get wrong rows, and will now get a hard query 
failure instead. That's the right trade since the old answer was incorrect, but 
it is a behavior change worth a line in the release notes so people know why a 
previously-accepted query now errors.



##########
pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/rules/PinotJoinExchangeNodeInsertRule.java:
##########
@@ -127,6 +127,62 @@ private static PinotLogicalExchange 
createExchangeForLookupJoin(PinotHintOptions
     }
   }
 
+  /// Rejects the pairs of distribution types that cannot produce the join's 
rows, which are otherwise silently
+  /// answered wrong. A pair is workable only if every pair of rows that could 
match meets on one worker, and only if
+  /// each row is emitted by one worker.
+  ///
+  /// Broadcasting a side gives every worker all of its rows, so it stands in 
for any partitioning of the other side.
+  /// Broadcasting both sides gives every worker all the rows of both, and 
each joined row is then emitted once per
+  /// worker. Broadcasting neither leaves both sides partitioned, and they 
only line up when both are partitioned the
+  /// same way on the join keys, which also requires the join to have keys to 
partition on.
+  ///
+  /// An outer join additionally emits the rows of its outer side that matched 
nothing, which a worker can only tell
+  /// once it holds every row of the opposite side. So the broadcast side must 
be the side opposite the outer one, and
+  /// a FULL join, whose two sides are both outer, cannot be answered with a 
broadcast side at all.
+  private static void 
validateDistributionTypes(PinotHintOptions.DistributionType 
leftDistributionType,
+      PinotHintOptions.DistributionType rightDistributionType, JoinInfo 
joinInfo, JoinRelType joinType) {
+    boolean leftBroadcast = leftDistributionType == 
PinotHintOptions.DistributionType.BROADCAST;
+    boolean rightBroadcast = rightDistributionType == 
PinotHintOptions.DistributionType.BROADCAST;
+    if (leftBroadcast && rightBroadcast) {
+      throw new IllegalArgumentException(
+          "Cannot broadcast both sides of the join: every worker would hold 
every row of both sides and emit each "
+              + "joined row, repeating the join result once per worker. 
Broadcast at most one side.");
+    }
+    if (leftBroadcast || rightBroadcast) {
+      switch (joinType) {
+        case LEFT:
+          Preconditions.checkArgument(rightBroadcast,
+              "LEFT join requires the right side to be the broadcast one: a 
worker can only tell that a left row "
+                  + "matched nothing if it holds every right row, got left: 
%s, right: %s", leftDistributionType,
+              rightDistributionType);
+          break;
+        case RIGHT:
+          Preconditions.checkArgument(leftBroadcast,
+              "RIGHT join requires the left side to be the broadcast one: a 
worker can only tell that a right row "
+                  + "matched nothing if it holds every left row, got left: %s, 
right: %s", leftDistributionType,
+              rightDistributionType);
+          break;
+        case FULL:
+          throw new IllegalArgumentException(
+              "FULL join cannot broadcast a side: both sides emit the rows 
that matched nothing, so neither side can "
+                  + "be the partitioned one. Distribute both sides by hash on 
the join keys.");
+        default:
+          break;
+      }
+      return;
+    }
+    Preconditions.checkArgument(!joinInfo.leftKeys.isEmpty(),
+        "Join without equi keys requires one side to be broadcast: with both 
sides partitioned, a pair of rows that "
+            + "could match may land on different workers, got left: %s, right: 
%s", leftDistributionType,
+        rightDistributionType);
+    Preconditions.checkArgument(
+        leftDistributionType == rightDistributionType
+            && leftDistributionType != 
PinotHintOptions.DistributionType.RANDOM,
+        "Both sides of the join must be distributed the same way on the join 
keys when neither side is broadcast, "
+            + "otherwise rows sharing a key land on different workers, got 
left: %s, right: %s", leftDistributionType,

Review Comment:
   Minor: `local`/`local` passes here on trust, without checking the two tables 
are actually co-partitioned or one is replicated. That matches the existing 
colocated-hint philosophy of trusting the user, so no change needed — just 
calling out that this pair is accepted on faith while the others are reasoned 
about.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to