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);