Skip to content

Commit 55a9af0

Browse files
committed
Expand tests to use random bounds, after multi-thread fix
Also fix an issue around nulls for out-of-bounds results And investigate and affirm that the use of -1 internally to mark invalid grid-ids is fine for all three grid types (ie. -1 is not a valid grid-id in any of these cases, for different reasons in each).
1 parent bf3b40d commit 55a9af0

File tree

7 files changed

+44
-21
lines changed

7 files changed

+44
-21
lines changed

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ public long calculateGridId(Point point) {
6666
if (bounds.validHash(geohash)) {
6767
return Geohash.longEncode(geohash);
6868
}
69-
// TODO: Are negative values allowed in geohash long encoding?
69+
// Geohash uses the lowest 4 bites for precision, and if all four are set, we get 15, which is invalid since the
70+
// max precision allowed is 12. This allows us to return -1 to indicate invalid geohashes (since all bits are set to 1).
7071
return -1;
7172
}
7273

@@ -209,7 +210,8 @@ public Object fold(FoldContext ctx) {
209210
} else {
210211
GeoBoundingBox bbox = asGeoBoundingBox(bounds().fold(ctx));
211212
GeoHashBoundedGrid bounds = new GeoHashBoundedGrid(precision, bbox);
212-
return bounds.calculateGridId(GEO.wkbAsPoint(point));
213+
long gridId = bounds.calculateGridId(GEO.wkbAsPoint(point));
214+
return gridId < 0 ? null : gridId;
213215
}
214216
}
215217

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public long calculateGridId(Point point) {
6464
if (bounds.validHex(geohex)) {
6565
return geohex;
6666
}
67-
// TODO: Are we sure negative numbers are not valid
67+
// H3 explicitly requires the highest bit to be zero, freeing up all negative numbers as invalid ids. See H3.isValidHex()
6868
return -1L;
6969
}
7070

@@ -212,7 +212,8 @@ public Object fold(FoldContext ctx) {
212212
} else {
213213
GeoBoundingBox bbox = asGeoBoundingBox(bounds().fold(ctx));
214214
GeoHexBoundedGrid bounds = new GeoHexBoundedGrid(precision, bbox);
215-
return bounds.calculateGridId(GEO.wkbAsPoint(point));
215+
long gridId = bounds.calculateGridId(GEO.wkbAsPoint(point));
216+
return gridId < 0 ? null : gridId;
216217
}
217218
}
218219

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ public long calculateGridId(Point point) {
6969
if (bounds.validTile(x, y, precision)) {
7070
return GeoTileUtils.longEncodeTiles(precision, x, y);
7171
}
72-
// TODO: Are we sure negative numbers are not valid
72+
// GeoTileUtils uses the highest 6 bits to store the zoom level. However, MAX_ZOOM is 29, which takes 5 bits.
73+
// This leaves the sign bit unused, so it can be used to indicate an invalid tile.
7374
return -1L;
7475
}
7576

@@ -203,7 +204,8 @@ public Object fold(FoldContext ctx) {
203204
} else {
204205
GeoBoundingBox bbox = asGeoBoundingBox(bounds().fold(ctx));
205206
GeoTileBoundedGrid bounds = new GeoTileBoundedGrid(precision, bbox);
206-
return bounds.calculateGridId(GEO.wkbAsPoint(point));
207+
long gridId = bounds.calculateGridId(GEO.wkbAsPoint(point));
208+
return gridId < 0 ? null : gridId;
207209
}
208210
}
209211

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

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,21 +102,17 @@ protected static void addTestCaseSuppliers(
102102
BytesRef geometry = (BytesRef) geoTypedData.data();
103103
int precision = between(1, 8);
104104
TestCaseSupplier.TypedData precisionData = new TestCaseSupplier.TypedData(precision, INTEGER, "precision");
105-
Rectangle bounds = new Rectangle(-180, 180, 90, -90);
106105
String evaluatorName = "FromFieldAndLiteralAndLiteralEvaluator[in=Attribute[channel=0], bounds=[";
107106
if (literalPrecision) {
108107
precisionData = precisionData.forceLiteral();
109108
evaluatorName = "FromFieldAndLiteralAndLiteralEvaluator[in=Attribute[channel=0]";
110109
}
111-
TestCaseSupplier.TypedData boundsData;
112-
// Create a rectangle as bounds
113-
BytesRef boundsBytesRef = GEO.asWkb(bounds);
114-
boundsData = new TestCaseSupplier.TypedData(boundsBytesRef, GEO_SHAPE, "bounds").forceLiteral();
110+
var boundsData = randomBoundsData();
115111
return new TestCaseSupplier.TestCase(
116-
List.of(geoTypedData, precisionData, boundsData),
112+
List.of(geoTypedData, precisionData, boundsData.typedData),
117113
startsWith(getFunctionClassName() + evaluatorName),
118114
LONG,
119-
equalTo(expectedValueWithBounds.apply(geometry, precision, SpatialGridFunction.asGeoBoundingBox(bounds)))
115+
equalTo(expectedValueWithBounds.apply(geometry, precision, boundsData.geoBoundingBox()))
120116
);
121117
}));
122118
}
@@ -141,6 +137,25 @@ public static TestCaseSupplier.TypedDataSupplier testCaseSupplier(DataType dataT
141137
}
142138
}
143139

140+
protected record TestBoundsData(GeoBoundingBox geoBoundingBox, TestCaseSupplier.TypedData typedData) {}
141+
142+
private static TestBoundsData randomBoundsData() {
143+
// Create a rectangle with random center and random size, that does not exceed geographic bounds
144+
double x = randomDoubleBetween(-180.1, 179.1, false);
145+
double y = randomDoubleBetween(-90.1, 89.1, false);
146+
double width = randomDoubleBetween(0.1, 180.0, true);
147+
double height = randomDoubleBetween(0.1, 90.0, true);
148+
double minX = Math.max(-180, x - width / 2);
149+
double maxX = Math.min(180, x + width / 2);
150+
double minY = Math.max(-90, y - height / 2);
151+
double maxY = Math.min(90, y + height / 2);
152+
Rectangle bounds = new Rectangle(minX, maxX, maxY, minY);
153+
return new TestBoundsData(
154+
SpatialGridFunction.asGeoBoundingBox(bounds),
155+
new TestCaseSupplier.TypedData(GEO.asWkb(bounds), GEO_SHAPE, "bounds").forceLiteral()
156+
);
157+
}
158+
144159
protected Long process(int precision, BiFunction<BytesRef, Integer, Long> expectedValue) {
145160
Object spatialObj = this.testCase.getDataValues().getFirst();
146161
assumeNotNull(spatialObj);

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,10 @@ private static long valueOf(BytesRef wkb, int precision) {
5050
return StGeohash.unboundedGrid.calculateGridId(UNSPECIFIED.wkbAsPoint(wkb), precision);
5151
}
5252

53-
private static long boundedValueOf(BytesRef wkb, int precision, GeoBoundingBox bbox) {
53+
private static Long boundedValueOf(BytesRef wkb, int precision, GeoBoundingBox bbox) {
5454
StGeohash.GeoHashBoundedGrid bounds = new StGeohash.GeoHashBoundedGrid.Factory(precision, bbox).get(null);
55-
return bounds.calculateGridId(UNSPECIFIED.wkbAsPoint(wkb));
55+
long gridId = bounds.calculateGridId(UNSPECIFIED.wkbAsPoint(wkb));
56+
return gridId < 0 ? null : gridId;
5657
}
5758

5859
@Override

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,10 @@ private static long valueOf(BytesRef wkb, int precision) {
5050
return StGeohex.unboundedGrid.calculateGridId(UNSPECIFIED.wkbAsPoint(wkb), precision);
5151
}
5252

53-
private static long boundedValueOf(BytesRef wkb, int precision, GeoBoundingBox bbox) {
53+
private static Long boundedValueOf(BytesRef wkb, int precision, GeoBoundingBox bbox) {
5454
StGeohex.GeoHexBoundedGrid bounds = new StGeohex.GeoHexBoundedGrid.Factory(precision, bbox).get(null);
55-
return bounds.calculateGridId(UNSPECIFIED.wkbAsPoint(wkb));
55+
long gridId = bounds.calculateGridId(UNSPECIFIED.wkbAsPoint(wkb));
56+
return gridId < 0 ? null : gridId;
5657
}
5758

5859
@Override

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import org.apache.lucene.util.BytesRef;
1414
import org.elasticsearch.common.geo.GeoBoundingBox;
1515
import org.elasticsearch.license.License;
16-
import org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileUtils;
1716
import org.elasticsearch.xpack.esql.core.expression.Expression;
1817
import org.elasticsearch.xpack.esql.core.tree.Source;
1918
import org.elasticsearch.xpack.esql.core.type.DataType;
@@ -23,6 +22,7 @@
2322
import java.util.List;
2423
import java.util.function.Supplier;
2524

25+
import static org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileUtils.MAX_ZOOM;
2626
import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.UNSPECIFIED;
2727
import static org.hamcrest.Matchers.containsString;
2828

@@ -50,9 +50,10 @@ private static long valueOf(BytesRef wkb, int precision) {
5050
return StGeotile.unboundedGrid.calculateGridId(UNSPECIFIED.wkbAsPoint(wkb), precision);
5151
}
5252

53-
private static long boundedValueOf(BytesRef wkb, int precision, GeoBoundingBox bbox) {
53+
private static Long boundedValueOf(BytesRef wkb, int precision, GeoBoundingBox bbox) {
5454
StGeotile.GeoTileBoundedGrid bounds = new StGeotile.GeoTileBoundedGrid.Factory(precision, bbox).get(null);
55-
return bounds.calculateGridId(UNSPECIFIED.wkbAsPoint(wkb));
55+
long gridId = bounds.calculateGridId(UNSPECIFIED.wkbAsPoint(wkb));
56+
return gridId < 0 ? null : gridId;
5657
}
5758

5859
@Override
@@ -64,7 +65,7 @@ protected Expression build(Source source, List<Expression> args) {
6465
public void testInvalidPrecision() {
6566
IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () -> process(-1, StGeotileTests::valueOf));
6667
assertThat(ex.getMessage(), containsString("Invalid geotile_grid precision of -1. Must be between 0 and 29."));
67-
ex = expectThrows(IllegalArgumentException.class, () -> process(GeoTileUtils.MAX_ZOOM + 1, StGeotileTests::valueOf));
68+
ex = expectThrows(IllegalArgumentException.class, () -> process(MAX_ZOOM + 1, StGeotileTests::valueOf));
6869
assertThat(ex.getMessage(), containsString("Invalid geotile_grid precision of 30. Must be between 0 and 29."));
6970
}
7071
}

0 commit comments

Comments
 (0)