|
17 | 17 |
|
18 | 18 | # Tests for pushing a TopK (Sort with fetch) through an outer join. |
19 | 19 | # |
20 | | -# These queries exercise the scenarios handled by the PushDownTopKThroughJoin |
21 | | -# rule. That rule lands in a follow-up PR; the EXPLAIN plans below capture |
22 | | -# current behavior, so the follow-up's diff shows exactly which plans change. |
23 | | -# The query-result checks hold whether or not the rule is enabled. |
| 20 | +# These queries exercise the scenarios handled by the `push_down_limit` |
| 21 | +# rule's TopK-through-join rewrite (see |
| 22 | +# `datafusion/optimizer/src/push_down_limit/topk_through_join.rs`). |
24 | 23 |
|
25 | 24 | statement ok |
26 | 25 | set datafusion.execution.target_partitions = 1; |
@@ -1139,6 +1138,223 @@ logical_plan |
1139 | 1138 | 09)----------------TableScan: t1 projection=[a] |
1140 | 1139 | 10)--------TableScan: t2 projection=[x] |
1141 | 1140 |
|
| 1141 | +### |
| 1142 | +### Degenerate cases: LIMIT 0, sort keys with no column references |
| 1143 | +### |
| 1144 | + |
| 1145 | +# LIMIT 0 is folded away to an EmptyRelation by an earlier optimizer pass |
| 1146 | +# before the TopK-through-join rewrite ever sees it. |
| 1147 | +query TT |
| 1148 | +EXPLAIN SELECT t1.a, t1.b, t2.x |
| 1149 | +FROM t1 LEFT JOIN t2 ON t1.a = t2.x |
| 1150 | +ORDER BY t1.b ASC LIMIT 0; |
| 1151 | +---- |
| 1152 | +logical_plan EmptyRelation: rows=0 |
| 1153 | + |
| 1154 | +query III |
| 1155 | +SELECT t1.a, t1.b, t2.x |
| 1156 | +FROM t1 LEFT JOIN t2 ON t1.a = t2.x |
| 1157 | +ORDER BY t1.b ASC LIMIT 0; |
| 1158 | +---- |
| 1159 | + |
| 1160 | +# A sort key with no column references at all is trivially "entirely from" |
| 1161 | +# either side, so it pushes to the first candidate side (left, for LEFT |
| 1162 | +# JOIN). `1 + 1` is constant-folded to `2` (aliased back to its original |
| 1163 | +# display name) by an earlier optimizer pass. |
| 1164 | +query TT |
| 1165 | +EXPLAIN SELECT t1.a, t1.b, t2.x |
| 1166 | +FROM t1 LEFT JOIN t2 ON t1.a = t2.x |
| 1167 | +ORDER BY 1 + 1 LIMIT 3; |
| 1168 | +---- |
| 1169 | +logical_plan |
| 1170 | +01)Sort: Int64(2) AS Int64(1) + Int64(1) ASC NULLS LAST, fetch=3 |
| 1171 | +02)--Left Join: t1.a = t2.x |
| 1172 | +03)----Sort: Int64(2) AS Int64(1) + Int64(1) ASC NULLS LAST, fetch=3 |
| 1173 | +04)------TableScan: t1 projection=[a, b] |
| 1174 | +05)----TableScan: t2 projection=[x] |
| 1175 | + |
| 1176 | +query III |
| 1177 | +SELECT t1.a, t1.b, t2.x |
| 1178 | +FROM t1 LEFT JOIN t2 ON t1.a = t2.x |
| 1179 | +ORDER BY 1 + 1 LIMIT 3; |
| 1180 | +---- |
| 1181 | +1 10 1 |
| 1182 | +2 20 2 |
| 1183 | +3 30 3 |
| 1184 | + |
| 1185 | +### |
| 1186 | +### JoinConstraint::Using |
| 1187 | +### |
| 1188 | + |
| 1189 | +statement ok |
| 1190 | +CREATE TABLE t_using1 (a INT, b INT) AS VALUES (1, 10), (2, 20), (3, 30); |
| 1191 | + |
| 1192 | +statement ok |
| 1193 | +CREATE TABLE t_using2 (a INT, y INT) AS VALUES (1, 100), (2, 200); |
| 1194 | + |
| 1195 | +# A USING join wraps the join in a Projection (to coalesce the shared join |
| 1196 | +# column); the rewrite still peels through that Projection to reach the Join. |
| 1197 | +query TT |
| 1198 | +EXPLAIN SELECT t_using1.a, t_using1.b, t_using2.y |
| 1199 | +FROM t_using1 LEFT JOIN t_using2 USING (a) |
| 1200 | +ORDER BY t_using1.b ASC LIMIT 2; |
| 1201 | +---- |
| 1202 | +logical_plan |
| 1203 | +01)Sort: t_using1.b ASC NULLS LAST, fetch=2 |
| 1204 | +02)--Projection: t_using1.a, t_using1.b, t_using2.y |
| 1205 | +03)----Left Join: Using t_using1.a = t_using2.a |
| 1206 | +04)------Sort: t_using1.b ASC NULLS LAST, fetch=2 |
| 1207 | +05)--------TableScan: t_using1 projection=[a, b] |
| 1208 | +06)------TableScan: t_using2 projection=[a, y] |
| 1209 | + |
| 1210 | +query III |
| 1211 | +SELECT t_using1.a, t_using1.b, t_using2.y |
| 1212 | +FROM t_using1 LEFT JOIN t_using2 USING (a) |
| 1213 | +ORDER BY t_using1.b ASC LIMIT 2; |
| 1214 | +---- |
| 1215 | +1 10 100 |
| 1216 | +2 20 200 |
| 1217 | + |
| 1218 | +statement ok |
| 1219 | +DROP TABLE t_using1; |
| 1220 | + |
| 1221 | +statement ok |
| 1222 | +DROP TABLE t_using2; |
| 1223 | + |
| 1224 | +### |
| 1225 | +### Mark joins produced from EXISTS combined with OR |
| 1226 | +### |
| 1227 | + |
| 1228 | +# `t1.b > 25 OR EXISTS (...)` decorrelates to a LeftMark join, but — unlike |
| 1229 | +# a bare `EXISTS` decorrelating to LeftSemi — the OR needs the mark's boolean |
| 1230 | +# value, so a Filter always sits directly above the LeftMark join to combine |
| 1231 | +# it with the other predicate. That Filter is not one of the transparent |
| 1232 | +# nodes this rewrite peels through, so pushdown is (currently) blocked for |
| 1233 | +# every mark join reachable this way from SQL, even though the LeftMark |
| 1234 | +# preserved-side guarantee itself holds (see the unit tests |
| 1235 | +# `topk_pushed_to_left_of_left_mark_join` / `..._right_mark_join`). |
| 1236 | +query TT |
| 1237 | +EXPLAIN SELECT t1.a, t1.b |
| 1238 | +FROM t1 |
| 1239 | +WHERE t1.b > 25 OR EXISTS (SELECT 1 FROM t2 WHERE t2.x = t1.a) |
| 1240 | +ORDER BY t1.b ASC LIMIT 3; |
| 1241 | +---- |
| 1242 | +logical_plan |
| 1243 | +01)Sort: t1.b ASC NULLS LAST, fetch=3 |
| 1244 | +02)--Projection: t1.a, t1.b |
| 1245 | +03)----Filter: t1.b > Int32(25) OR __correlated_sq_1.mark |
| 1246 | +04)------LeftMark Join: t1.a = __correlated_sq_1.x |
| 1247 | +05)--------TableScan: t1 projection=[a, b] |
| 1248 | +06)--------SubqueryAlias: __correlated_sq_1 |
| 1249 | +07)----------TableScan: t2 projection=[x] |
| 1250 | + |
| 1251 | +query II |
| 1252 | +SELECT t1.a, t1.b |
| 1253 | +FROM t1 |
| 1254 | +WHERE t1.b > 25 OR EXISTS (SELECT 1 FROM t2 WHERE t2.x = t1.a) |
| 1255 | +ORDER BY t1.b ASC LIMIT 3; |
| 1256 | +---- |
| 1257 | +1 10 |
| 1258 | +2 20 |
| 1259 | +3 30 |
| 1260 | + |
| 1261 | +### |
| 1262 | +### Sort key is a projected expression, not a plain column |
| 1263 | +### |
| 1264 | + |
| 1265 | +# The outer ORDER BY references a projected expression built only from |
| 1266 | +# preserved-side columns. The rule resolves neg_b through the Projection to |
| 1267 | +# (- t1.b) and pushes a Sort on that expression below the join. |
| 1268 | +query TT |
| 1269 | +EXPLAIN SELECT * FROM ( |
| 1270 | + SELECT t1.a, -t1.b AS neg_b, t2.y |
| 1271 | + FROM t1 LEFT JOIN t2 ON t1.a = t2.x |
| 1272 | +) sub |
| 1273 | +ORDER BY neg_b ASC LIMIT 3; |
| 1274 | +---- |
| 1275 | +logical_plan |
| 1276 | +01)Sort: sub.neg_b ASC NULLS LAST, fetch=3 |
| 1277 | +02)--SubqueryAlias: sub |
| 1278 | +03)----Projection: t1.a, (- t1.b) AS neg_b, t2.y |
| 1279 | +04)------Left Join: t1.a = t2.x |
| 1280 | +05)--------Sort: (- t1.b) ASC NULLS LAST, fetch=3 |
| 1281 | +06)----------TableScan: t1 projection=[a, b] |
| 1282 | +07)--------TableScan: t2 projection=[x, y] |
| 1283 | + |
| 1284 | +query III |
| 1285 | +SELECT * FROM ( |
| 1286 | + SELECT t1.a, -t1.b AS neg_b, t2.y |
| 1287 | + FROM t1 LEFT JOIN t2 ON t1.a = t2.x |
| 1288 | +) sub |
| 1289 | +ORDER BY neg_b ASC LIMIT 3; |
| 1290 | +---- |
| 1291 | +5 -50 NULL |
| 1292 | +4 -40 NULL |
| 1293 | +3 -30 300 |
| 1294 | + |
| 1295 | +# Negative case: the projected expression mixes both sides. After resolving |
| 1296 | +# through the Projection the key references t2.y, so nothing is pushed. |
| 1297 | +query TT |
| 1298 | +EXPLAIN SELECT * FROM ( |
| 1299 | + SELECT t1.a, t1.b + coalesce(t2.y, 0) AS mixed |
| 1300 | + FROM t1 LEFT JOIN t2 ON t1.a = t2.x |
| 1301 | +) sub |
| 1302 | +ORDER BY mixed DESC LIMIT 3; |
| 1303 | +---- |
| 1304 | +logical_plan |
| 1305 | +01)Sort: sub.mixed DESC NULLS FIRST, fetch=3 |
| 1306 | +02)--SubqueryAlias: sub |
| 1307 | +03)----Projection: t1.a, CAST(t1.b AS Int64) + CASE WHEN __common_expr_1 IS NOT NULL THEN __common_expr_1 ELSE Int64(0) END AS mixed |
| 1308 | +04)------Projection: CAST(t2.y AS Int64) AS __common_expr_1, t1.a, t1.b |
| 1309 | +05)--------Left Join: t1.a = t2.x |
| 1310 | +06)----------TableScan: t1 projection=[a, b] |
| 1311 | +07)----------TableScan: t2 projection=[x, y] |
| 1312 | + |
| 1313 | +query II |
| 1314 | +SELECT * FROM ( |
| 1315 | + SELECT t1.a, t1.b + coalesce(t2.y, 0) AS mixed |
| 1316 | + FROM t1 LEFT JOIN t2 ON t1.a = t2.x |
| 1317 | +) sub |
| 1318 | +ORDER BY mixed DESC LIMIT 3; |
| 1319 | +---- |
| 1320 | +3 330 |
| 1321 | +2 220 |
| 1322 | +1 110 |
| 1323 | + |
| 1324 | +# Two stacked Projections: the key is computed in the inner one and re-aliased |
| 1325 | +# in the outer one. Resolution must substitute through both layers. |
| 1326 | +query TT |
| 1327 | +EXPLAIN SELECT a, k2 FROM ( |
| 1328 | + SELECT a, k1 AS k2 FROM ( |
| 1329 | + SELECT t1.a, t1.b * 2 AS k1, t2.y |
| 1330 | + FROM t1 LEFT JOIN t2 ON t1.a = t2.x |
| 1331 | + ) s1 |
| 1332 | +) s2 |
| 1333 | +ORDER BY k2 DESC LIMIT 2; |
| 1334 | +---- |
| 1335 | +logical_plan |
| 1336 | +01)Sort: s2.k2 DESC NULLS FIRST, fetch=2 |
| 1337 | +02)--SubqueryAlias: s2 |
| 1338 | +03)----Projection: s1.a, s1.k1 AS k2 |
| 1339 | +04)------SubqueryAlias: s1 |
| 1340 | +05)--------Projection: t1.a, CAST(t1.b AS Int64) * Int64(2) AS k1 |
| 1341 | +06)----------Left Join: t1.a = t2.x |
| 1342 | +07)------------Sort: CAST(t1.b AS Int64) * Int64(2) DESC NULLS FIRST, fetch=2 |
| 1343 | +08)--------------TableScan: t1 projection=[a, b] |
| 1344 | +09)------------TableScan: t2 projection=[x] |
| 1345 | + |
| 1346 | +query II |
| 1347 | +SELECT a, k2 FROM ( |
| 1348 | + SELECT a, k1 AS k2 FROM ( |
| 1349 | + SELECT t1.a, t1.b * 2 AS k1, t2.y |
| 1350 | + FROM t1 LEFT JOIN t2 ON t1.a = t2.x |
| 1351 | + ) s1 |
| 1352 | +) s2 |
| 1353 | +ORDER BY k2 DESC LIMIT 2; |
| 1354 | +---- |
| 1355 | +5 100 |
| 1356 | +4 80 |
| 1357 | + |
1142 | 1358 | statement ok |
1143 | 1359 | set datafusion.execution.target_partitions = 4; |
1144 | 1360 |
|
|
0 commit comments