diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java index b05cbb56afa6d..9f5d6440e4a06 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java @@ -4986,51 +4986,56 @@ public void testPushTopNDistanceWithCompoundFilterToSource() { /** * Tests that multiple sorts, including distance and a field, are pushed down to the source. * - * ProjectExec[[abbrev{f}#21, name{f}#22, location{f}#25, country{f}#26, city{f}#27, scalerank{f}#23, scale{r}#7]] - * \_TopNExec[[Order[distance{r}#4,ASC,LAST], Order[scalerank{f}#23,ASC,LAST], Order[scale{r}#7,DESC,FIRST]],5[INTEGER],0] - * \_ExchangeExec[[abbrev{f}#21, name{f}#22, location{f}#25, country{f}#26, city{f}#27, scalerank{f}#23, scale{r}#7, - * distance{r}#4],false] - * \_ProjectExec[[abbrev{f}#21, name{f}#22, location{f}#25, country{f}#26, city{f}#27, scalerank{f}#23, scale{r}#7, - * distance{r}#4]] - * \_FieldExtractExec[abbrev{f}#21, name{f}#22, country{f}#26, city{f}#27][] + * ProjectExec[[abbrev{f}#25, name{f}#26, location{f}#29, country{f}#30, city{f}#31, scalerank{f}#27, scale{r}#7]] + * \_TopNExec[[ + * Order[distance{r}#4,ASC,LAST], + * Order[scalerank{f}#27,ASC,LAST], + * Order[scale{r}#7,DESC,FIRST], + * Order[loc{r}#10,DESC,FIRST] + * ],5[INTEGER],0] + * \_ExchangeExec[[abbrev{f}#25, name{f}#26, location{f}#29, country{f}#30, city{f}#31, scalerank{f}#27, scale{r}#7, + * distance{r}#4, loc{r}#10],false] + * \_ProjectExec[[abbrev{f}#25, name{f}#26, location{f}#29, country{f}#30, city{f}#31, scalerank{f}#27, scale{r}#7, + * distance{r}#4, loc{r}#10]] + * \_FieldExtractExec[abbrev{f}#25, name{f}#26, country{f}#30, city{f}#31][] * \_EvalExec[[ - * STDISTANCE(location{f}#25,[1 1 0 0 0 e1 7a 14 ae 47 21 29 40 a0 1a 2f dd 24 d6 4b 40][GEO_POINT]) AS distance, - * 10[INTEGER] - scalerank{f}#23 AS scale + * STDISTANCE(location{f}#29,[1 1 0 0 0 e1 7a 14 ae 47 21 29 40 a0 1a 2f dd 24 d6 4b 40][GEO_POINT]) AS distance, + * 10[INTEGER] - scalerank{f}#27 AS scale, TOSTRING(location{f}#29) AS loc * ]] - * \_FieldExtractExec[location{f}#25, scalerank{f}#23][] + * \_FieldExtractExec[location{f}#29, scalerank{f}#27][] * \_EsQueryExec[airports], indexMode[standard], query[{ * "bool":{ * "filter":[ - * {"esql_single_value":{"field":"scalerank","next":{"range":{...}},"source":"scalerank < 6@3:31"}}, + * {"esql_single_value":{"field":"scalerank","next":{...},"source":"scalerank < 6@3:31"}}, * {"bool":{ * "must":[ * {"geo_shape":{"location":{"relation":"INTERSECTS","shape":{...}}}}, * {"geo_shape":{"location":{"relation":"DISJOINT","shape":{...}}}} - * ],"boost":1.0}} - * ],"boost":1.0}}][_doc{f}#39], limit[5], sort[[ - * GeoDistanceSort[field=location{f}#25, direction=ASC, lat=55.673, lon=12.565], - * FieldSort[field=scalerank{f}#23, direction=ASC, nulls=LAST] - * ]] estimatedRowSize[253] + * ],"boost":1.0}}],"boost":1.0}}][_doc{f}#44], limit[5], sort[[ + * GeoDistanceSort[field=location{f}#29, direction=ASC, lat=55.673, lon=12.565], + * FieldSort[field=scalerank{f}#27, direction=ASC, nulls=LAST] + * ]] estimatedRowSize[303] * */ public void testPushTopNDistanceAndPushableFieldWithCompoundFilterToSource() { var optimized = optimizedPlan(physicalPlan(""" FROM airports - | EVAL distance = ST_DISTANCE(location, TO_GEOPOINT("POINT(12.565 55.673)")), scale = 10 - scalerank + | EVAL distance = ST_DISTANCE(location, TO_GEOPOINT("POINT(12.565 55.673)")), scale = 10 - scalerank, loc = location::string | WHERE distance < 500000 AND scalerank < 6 AND distance > 10000 - | SORT distance ASC, scalerank ASC, scale DESC + | SORT distance ASC, scalerank ASC, scale DESC, loc DESC | LIMIT 5 | KEEP abbrev, name, location, country, city, scalerank, scale """, airports)); var project = as(optimized, ProjectExec.class); var topN = as(project.child(), TopNExec.class); + assertThat(topN.order().size(), is(4)); var exchange = asRemoteExchange(topN.child()); project = as(exchange.child(), ProjectExec.class); assertThat( names(project.projections()), - contains("abbrev", "name", "location", "country", "city", "scalerank", "scale", "distance") + contains("abbrev", "name", "location", "country", "city", "scalerank", "scale", "distance", "loc") ); var extract = as(project.child(), FieldExtractExec.class); assertThat(names(extract.attributesToExtract()), contains("abbrev", "name", "country", "city")); @@ -5073,8 +5078,7 @@ public void testPushTopNDistanceAndPushableFieldWithCompoundFilterToSource() { } /** - * This test shows that with an additional EVAL used in the filter, we can no longer push down the SORT distance. - * TODO: This could be optimized in future work. Consider moving much of EnableSpatialDistancePushdown into logical planning. + * This test shows that if the filter contains a predicate on the same field that is sorted, we cannot push down the sort. * * ProjectExec[[abbrev{f}#23, name{f}#24, location{f}#27, country{f}#28, city{f}#29, scalerank{f}#25 AS scale]] * \_TopNExec[[Order[distance{r}#4,ASC,LAST], Order[scalerank{f}#25,ASC,LAST]],5[INTEGER],0] @@ -5110,6 +5114,7 @@ public void testPushTopNDistanceAndNonPushableEvalWithCompoundFilterToSource() { var project = as(optimized, ProjectExec.class); var topN = as(project.child(), TopNExec.class); + assertThat(topN.order().size(), is(2)); var exchange = asRemoteExchange(topN.child()); project = as(exchange.child(), ProjectExec.class); @@ -5148,7 +5153,7 @@ public void testPushTopNDistanceAndNonPushableEvalWithCompoundFilterToSource() { } /** - * This test further shows that with a non-aliasing function, with the same name, less gets pushed down. + * This test shows that if the filter contains a predicate on the same field that is sorted, we cannot push down the sort. * * ProjectExec[[abbrev{f}#23, name{f}#24, location{f}#27, country{f}#28, city{f}#29, scale{r}#10]] * \_TopNExec[[Order[distance{r}#4,ASC,LAST], Order[scale{r}#10,ASC,LAST]],5[INTEGER],0] @@ -5185,6 +5190,7 @@ public void testPushTopNDistanceAndNonPushableEvalsWithCompoundFilterToSource() """, airports)); var project = as(optimized, ProjectExec.class); var topN = as(project.child(), TopNExec.class); + assertThat(topN.order().size(), is(2)); var exchange = asRemoteExchange(topN.child()); project = as(exchange.child(), ProjectExec.class); @@ -5222,7 +5228,8 @@ public void testPushTopNDistanceAndNonPushableEvalsWithCompoundFilterToSource() } /** - * This test shows that with if the top level AND'd predicate contains a non-pushable component, we should not push anything. + * This test shows that with if the top level predicate contains a non-pushable component (eg. disjunction), + * we should not push down the filter. * * ProjectExec[[abbrev{f}#8612, name{f}#8613, location{f}#8616, country{f}#8617, city{f}#8618, scalerank{f}#8614 AS scale]] * \_TopNExec[[Order[distance{r}#8596,ASC,LAST], Order[scalerank{f}#8614,ASC,LAST]],5[INTEGER],0] @@ -5260,6 +5267,7 @@ public void testPushTopNDistanceWithCompoundFilterToSourceAndDisjunctiveNonPusha var project = as(optimized, ProjectExec.class); var topN = as(project.child(), TopNExec.class); + assertThat(topN.order().size(), is(2)); var exchange = asRemoteExchange(topN.child()); project = as(exchange.child(), ProjectExec.class);