|
40 | 40 | * |
41 | 41 | * <p>Covers: |
42 | 42 | * <ul> |
43 | | - * <li>Join-type allowlist (INNER, LEFT OUTER, LEFT SEMI, LEFT ANTI) — |
44 | | - * RIGHT / FULL rejected. |
| 43 | + * <li>Join-type allowlist accepts all equi-join families (INNER, LEFT/RIGHT |
| 44 | + * OUTER, LEFT/RIGHT SEMI, LEFT/RIGHT ANTI, FULL OUTER); CROSS and NULL-aware |
| 45 | + * left anti stay rejected. |
45 | 46 | * <li>Position-preserving pairing accepts identity join keys. |
46 | 47 | * <li>Position-preserving pairing rejects swapped-column joins. |
47 | 48 | * <li>Partial shuffle coverage rejected. |
@@ -92,13 +93,81 @@ private static boolean invokeGate(ChildOutputPropertyGuarantor guarantor, |
92 | 93 | hint, joinType, left, right, leftShuffle, rightShuffle); |
93 | 94 | } |
94 | 95 |
|
| 96 | + /** |
| 97 | + * Installs the "same group, stable, non-empty" expectations shared by every |
| 98 | + * accepts-* test: colocate is not session-disabled, both tables are in the |
| 99 | + * same stable colocate group. Group ids 100L/200L match {@link #buildSide}. |
| 100 | + */ |
| 101 | + private static void expectStableSameGroup(ConnectContext connectContext, |
| 102 | + SessionVariable sessionVariable, |
| 103 | + GlobalStateMgr globalStateMgr, |
| 104 | + ColocateTableIndex colocateTableIndex, |
| 105 | + ColocateTableIndex.GroupId groupId) { |
| 106 | + new Expectations() { |
| 107 | + { |
| 108 | + ConnectContext.get(); |
| 109 | + result = connectContext; |
| 110 | + connectContext.getSessionVariable(); |
| 111 | + result = sessionVariable; |
| 112 | + sessionVariable.isDisableColocateJoin(); |
| 113 | + result = false; |
| 114 | + GlobalStateMgr.getCurrentState(); |
| 115 | + result = globalStateMgr; |
| 116 | + globalStateMgr.getColocateTableIndex(); |
| 117 | + result = colocateTableIndex; |
| 118 | + colocateTableIndex.isSameGroup(100L, 200L); |
| 119 | + result = true; |
| 120 | + colocateTableIndex.getGroup(100L); |
| 121 | + result = groupId; |
| 122 | + colocateTableIndex.getGroup(200L); |
| 123 | + result = groupId; |
| 124 | + colocateTableIndex.isGroupUnstable(groupId); |
| 125 | + result = false; |
| 126 | + } |
| 127 | + }; |
| 128 | + } |
| 129 | + |
| 130 | + /** |
| 131 | + * Asserts an identity-key join of {@code joinType} on a stable same-group |
| 132 | + * range-colocate pair is accepted (colocate = (1,2), shuffle = (1,2)). |
| 133 | + */ |
| 134 | + private void assertJoinTypeAccepted(TaskContext taskContext, ConnectContext connectContext, |
| 135 | + SessionVariable sessionVariable, GlobalStateMgr globalStateMgr, |
| 136 | + ColocateTableIndex colocateTableIndex, JoinOperator joinType) { |
| 137 | + ColocateTableIndex.GroupId groupId = new ColocateTableIndex.GroupId(1L, 1L); |
| 138 | + expectStableSameGroup(connectContext, sessionVariable, globalStateMgr, colocateTableIndex, groupId); |
| 139 | + assertTrue(invokeGate(newGuarantor(taskContext), joinType, |
| 140 | + buildSide(100L, 1, 2), buildSide(200L, 1, 2), cols(1, 2), cols(1, 2))); |
| 141 | + } |
| 142 | + |
95 | 143 | @Test |
96 | | - void rejectsRightOuterJoin(@Mocked TaskContext taskContext) { |
97 | | - // Join-type allowlist is the earliest gate; no ConnectContext access. |
98 | | - RangeDistributionSpec left = buildSide(100L, 1, 2); |
99 | | - RangeDistributionSpec right = buildSide(200L, 1, 2); |
100 | | - assertFalse(invokeGate(newGuarantor(taskContext), JoinOperator.RIGHT_OUTER_JOIN, |
101 | | - left, right, cols(1, 2), cols(1, 2))); |
| 144 | + void acceptsRightOuterJoin(@Mocked TaskContext taskContext, |
| 145 | + @Mocked ConnectContext connectContext, |
| 146 | + @Mocked SessionVariable sessionVariable, |
| 147 | + @Mocked GlobalStateMgr globalStateMgr, |
| 148 | + @Mocked ColocateTableIndex colocateTableIndex) { |
| 149 | + assertJoinTypeAccepted(taskContext, connectContext, sessionVariable, globalStateMgr, |
| 150 | + colocateTableIndex, JoinOperator.RIGHT_OUTER_JOIN); |
| 151 | + } |
| 152 | + |
| 153 | + @Test |
| 154 | + void acceptsRightSemiJoin(@Mocked TaskContext taskContext, |
| 155 | + @Mocked ConnectContext connectContext, |
| 156 | + @Mocked SessionVariable sessionVariable, |
| 157 | + @Mocked GlobalStateMgr globalStateMgr, |
| 158 | + @Mocked ColocateTableIndex colocateTableIndex) { |
| 159 | + assertJoinTypeAccepted(taskContext, connectContext, sessionVariable, globalStateMgr, |
| 160 | + colocateTableIndex, JoinOperator.RIGHT_SEMI_JOIN); |
| 161 | + } |
| 162 | + |
| 163 | + @Test |
| 164 | + void acceptsRightAntiJoin(@Mocked TaskContext taskContext, |
| 165 | + @Mocked ConnectContext connectContext, |
| 166 | + @Mocked SessionVariable sessionVariable, |
| 167 | + @Mocked GlobalStateMgr globalStateMgr, |
| 168 | + @Mocked ColocateTableIndex colocateTableIndex) { |
| 169 | + assertJoinTypeAccepted(taskContext, connectContext, sessionVariable, globalStateMgr, |
| 170 | + colocateTableIndex, JoinOperator.RIGHT_ANTI_JOIN); |
102 | 171 | } |
103 | 172 |
|
104 | 173 | @Test |
@@ -131,11 +200,33 @@ void rejectsBucketHint(@Mocked TaskContext taskContext) { |
131 | 200 | } |
132 | 201 |
|
133 | 202 | @Test |
134 | | - void rejectsFullOuterJoin(@Mocked TaskContext taskContext) { |
135 | | - // Join-type allowlist is the earliest gate; no ConnectContext access. |
| 203 | + void acceptsFullOuterJoin(@Mocked TaskContext taskContext, |
| 204 | + @Mocked ConnectContext connectContext, |
| 205 | + @Mocked SessionVariable sessionVariable, |
| 206 | + @Mocked GlobalStateMgr globalStateMgr, |
| 207 | + @Mocked ColocateTableIndex colocateTableIndex) { |
| 208 | + assertJoinTypeAccepted(taskContext, connectContext, sessionVariable, globalStateMgr, |
| 209 | + colocateTableIndex, JoinOperator.FULL_OUTER_JOIN); |
| 210 | + } |
| 211 | + |
| 212 | + @Test |
| 213 | + void rejectsCrossJoin(@Mocked TaskContext taskContext) { |
| 214 | + // CROSS join stays rejected by the allowlist (earliest gate after hint; |
| 215 | + // no ConnectContext access). It has no covering equijoin key anyway. |
| 216 | + RangeDistributionSpec left = buildSide(100L, 1, 2); |
| 217 | + RangeDistributionSpec right = buildSide(200L, 1, 2); |
| 218 | + assertFalse(invokeGate(newGuarantor(taskContext), JoinOperator.CROSS_JOIN, |
| 219 | + left, right, cols(1, 2), cols(1, 2))); |
| 220 | + } |
| 221 | + |
| 222 | + @Test |
| 223 | + void rejectsNullAwareLeftAntiJoin(@Mocked TaskContext taskContext) { |
| 224 | + // NULL-aware left anti (NOT IN with nullable) needs cross-bucket NULL |
| 225 | + // knowledge, which bucket-local colocate execution cannot provide, so it |
| 226 | + // stays rejected by the allowlist. |
136 | 227 | RangeDistributionSpec left = buildSide(100L, 1, 2); |
137 | 228 | RangeDistributionSpec right = buildSide(200L, 1, 2); |
138 | | - assertFalse(invokeGate(newGuarantor(taskContext), JoinOperator.FULL_OUTER_JOIN, |
| 229 | + assertFalse(invokeGate(newGuarantor(taskContext), JoinOperator.NULL_AWARE_LEFT_ANTI_JOIN, |
139 | 230 | left, right, cols(1, 2), cols(1, 2))); |
140 | 231 | } |
141 | 232 |
|
|
0 commit comments