Skip to content

Commit 14ea56a

Browse files
Test StDistance multivalue consistency and fixed two CartesianPoint bugs (elastic#114729) (elastic#114755)
1 parent 737f803 commit 14ea56a

File tree

6 files changed

+87
-8
lines changed

6 files changed

+87
-8
lines changed

x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/spatial/SpatialPushDownCartesianPointIT.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,10 @@ protected Geometry getQueryGeometry() {
3131
protected String castingFunction() {
3232
return "TO_CARTESIANSHAPE";
3333
}
34+
35+
@Override
36+
protected double searchDistance() {
37+
// We search much larger distances for Cartesian, to ensure we actually get results from the much wider data range
38+
return 1e12;
39+
}
3440
}

x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/spatial/SpatialPushDownGeoPointIT.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,9 @@ protected Geometry getQueryGeometry() {
3636
protected String castingFunction() {
3737
return "TO_GEOSHAPE";
3838
}
39+
40+
@Override
41+
protected double searchDistance() {
42+
return 10000000;
43+
}
3944
}

x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/spatial/SpatialPushDownPointsTestCase.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
package org.elasticsearch.xpack.esql.spatial;
99

10+
import org.elasticsearch.geometry.Geometry;
1011
import org.elasticsearch.geometry.Point;
1112
import org.elasticsearch.geometry.utils.GeometryValidator;
1213
import org.elasticsearch.geometry.utils.WellKnownText;
@@ -20,6 +21,7 @@
2021
import java.io.IOException;
2122
import java.text.ParseException;
2223
import java.util.ArrayList;
24+
import java.util.Arrays;
2325
import java.util.Locale;
2426

2527
import static org.hamcrest.Matchers.closeTo;
@@ -108,6 +110,61 @@ protected void assertFunction(String spatialFunction, String wkt, long expected,
108110
}
109111
}
110112

113+
public void testPushedDownDistanceSingleValue() throws RuntimeException {
114+
assertPushedDownDistance(false);
115+
}
116+
117+
public void testPushedDownDistanceMultiValue() throws RuntimeException {
118+
assertPushedDownDistance(true);
119+
}
120+
121+
private void assertPushedDownDistance(boolean multiValue) throws RuntimeException {
122+
initIndexes();
123+
for (int i = 0; i < random().nextInt(50, 100); i++) {
124+
if (multiValue) {
125+
final String[] values = new String[randomIntBetween(1, 5)];
126+
for (int j = 0; j < values.length; j++) {
127+
values[j] = "\"" + WellKnownText.toWKT(getIndexGeometry()) + "\"";
128+
}
129+
index("indexed", i + "", "{\"location\" : " + Arrays.toString(values) + " }");
130+
index("not-indexed", i + "", "{\"location\" : " + Arrays.toString(values) + " }");
131+
} else {
132+
final String value = WellKnownText.toWKT(getIndexGeometry());
133+
index("indexed", i + "", "{\"location\" : \"" + value + "\" }");
134+
index("not-indexed", i + "", "{\"location\" : \"" + value + "\" }");
135+
}
136+
}
137+
138+
refresh("indexed", "not-indexed");
139+
140+
for (int i = 0; i < 10; i++) {
141+
final Geometry geometry = getIndexGeometry();
142+
final String wkt = WellKnownText.toWKT(geometry);
143+
assertDistanceFunction(wkt);
144+
}
145+
}
146+
147+
protected abstract double searchDistance();
148+
149+
protected void assertDistanceFunction(String wkt) {
150+
String spatialFunction = "ST_DISTANCE";
151+
String castingFunction = castingFunction().replaceAll("SHAPE", "POINT");
152+
final String query1 = String.format(Locale.ROOT, """
153+
FROM indexed | WHERE %s(location, %s("%s")) < %.1f | STATS COUNT(*)
154+
""", spatialFunction, castingFunction, wkt, searchDistance());
155+
final String query2 = String.format(Locale.ROOT, """
156+
FROM not-indexed | WHERE %s(location, %s("%s")) < %.1f | STATS COUNT(*)
157+
""", spatialFunction, castingFunction, wkt, searchDistance());
158+
try (
159+
EsqlQueryResponse response1 = EsqlQueryRequestBuilder.newRequestBuilder(client()).query(query1).get();
160+
EsqlQueryResponse response2 = EsqlQueryRequestBuilder.newRequestBuilder(client()).query(query2).get();
161+
) {
162+
Object indexedResult = response1.response().column(0).iterator().next();
163+
Object notIndexedResult = response2.response().column(0).iterator().next();
164+
assertEquals(spatialFunction, indexedResult, notIndexedResult);
165+
}
166+
}
167+
111168
private String toString(CentroidCalculator centroid) {
112169
return "Centroid (x:" + centroid.getX() + ", y:" + centroid.getY() + ")";
113170
}

x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/spatial/SpatialPushDownTestCase.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
/**
2929
* Base class to check that a query than can be pushed down gives the same result
3030
* if it is actually pushed down and when it is executed by the compute engine,
31-
*
31+
* <p>
3232
* For doing that we create two indices, one fully indexed and another with index
3333
* and doc values disabled. Then we index the same data in both indices and we check
3434
* that the same ES|QL queries produce the same results in both.

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/BinarySpatialFunction.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.FIRST;
2929
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.SECOND;
3030
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isType;
31+
import static org.elasticsearch.xpack.esql.core.type.DataType.CARTESIAN_POINT;
32+
import static org.elasticsearch.xpack.esql.core.type.DataType.CARTESIAN_SHAPE;
3133
import static org.elasticsearch.xpack.esql.core.type.DataType.GEO_POINT;
3234
import static org.elasticsearch.xpack.esql.core.type.DataType.GEO_SHAPE;
3335
import static org.elasticsearch.xpack.esql.core.type.DataType.isNull;
@@ -203,7 +205,7 @@ public void setCrsType(DataType dataType) {
203205
}
204206

205207
private static final String[] GEO_TYPE_NAMES = new String[] { GEO_POINT.typeName(), GEO_SHAPE.typeName() };
206-
private static final String[] CARTESIAN_TYPE_NAMES = new String[] { GEO_POINT.typeName(), GEO_SHAPE.typeName() };
208+
private static final String[] CARTESIAN_TYPE_NAMES = new String[] { CARTESIAN_POINT.typeName(), CARTESIAN_SHAPE.typeName() };
207209

208210
protected static boolean spatialCRSCompatible(DataType spatialDataType, DataType otherDataType) {
209211
return DataType.isSpatialGeo(spatialDataType) && DataType.isSpatialGeo(otherDataType)

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/EnableSpatialDistancePushdown.java

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -216,31 +216,40 @@ private Expression rewriteDistanceFilter(
216216
Number number,
217217
ComparisonType comparisonType
218218
) {
219+
DataType shapeDataType = getShapeDataType(spatialExp);
219220
Geometry geometry = SpatialRelatesUtils.makeGeometryFromLiteral(literalExp);
220221
if (geometry instanceof Point point) {
221222
double distance = number.doubleValue();
222223
Source source = comparison.source();
223224
if (comparisonType.lt) {
224225
distance = comparisonType.eq ? distance : Math.nextDown(distance);
225-
return new SpatialIntersects(source, spatialExp, makeCircleLiteral(point, distance, literalExp));
226+
return new SpatialIntersects(source, spatialExp, makeCircleLiteral(point, distance, literalExp, shapeDataType));
226227
} else if (comparisonType.gt) {
227228
distance = comparisonType.eq ? distance : Math.nextUp(distance);
228-
return new SpatialDisjoint(source, spatialExp, makeCircleLiteral(point, distance, literalExp));
229+
return new SpatialDisjoint(source, spatialExp, makeCircleLiteral(point, distance, literalExp, shapeDataType));
229230
} else if (comparisonType.eq) {
230231
return new And(
231232
source,
232-
new SpatialIntersects(source, spatialExp, makeCircleLiteral(point, distance, literalExp)),
233-
new SpatialDisjoint(source, spatialExp, makeCircleLiteral(point, Math.nextDown(distance), literalExp))
233+
new SpatialIntersects(source, spatialExp, makeCircleLiteral(point, distance, literalExp, shapeDataType)),
234+
new SpatialDisjoint(source, spatialExp, makeCircleLiteral(point, Math.nextDown(distance), literalExp, shapeDataType))
234235
);
235236
}
236237
}
237238
return comparison;
238239
}
239240

240-
private Literal makeCircleLiteral(Point point, double distance, Expression literalExpression) {
241+
private Literal makeCircleLiteral(Point point, double distance, Expression literalExpression, DataType shapeDataType) {
241242
var circle = new Circle(point.getX(), point.getY(), distance);
242243
var wkb = WellKnownBinary.toWKB(circle, ByteOrder.LITTLE_ENDIAN);
243-
return new Literal(literalExpression.source(), new BytesRef(wkb), DataType.GEO_SHAPE);
244+
return new Literal(literalExpression.source(), new BytesRef(wkb), shapeDataType);
245+
}
246+
247+
private DataType getShapeDataType(Expression expression) {
248+
return switch (expression.dataType()) {
249+
case GEO_POINT, GEO_SHAPE -> DataType.GEO_SHAPE;
250+
case CARTESIAN_POINT, CARTESIAN_SHAPE -> DataType.CARTESIAN_SHAPE;
251+
default -> throw new IllegalArgumentException("Unsupported spatial data type: " + expression.dataType());
252+
};
244253
}
245254

246255
/**

0 commit comments

Comments
 (0)