diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohashFromFieldAndLiteralAndLiteralEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohashFromFieldAndLiteralAndLiteralEvaluator.java index 2e4b8e7d538d8..982252f3d125d 100644 --- a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohashFromFieldAndLiteralAndLiteralEvaluator.java +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohashFromFieldAndLiteralAndLiteralEvaluator.java @@ -7,6 +7,7 @@ import java.lang.IllegalArgumentException; import java.lang.Override; import java.lang.String; +import java.util.function.Function; import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.BytesRefBlock; import org.elasticsearch.compute.data.LongBlock; @@ -72,7 +73,7 @@ public LongBlock eval(int positionCount, BytesRefBlock inBlock) { @Override public String toString() { - return "StGeohashFromFieldAndLiteralAndLiteralEvaluator[" + "in=" + in + ", bounds=" + bounds + "]"; + return "StGeohashFromFieldAndLiteralAndLiteralEvaluator[" + "in=" + in + "]"; } @Override @@ -97,10 +98,10 @@ static class Factory implements EvalOperator.ExpressionEvaluator.Factory { private final EvalOperator.ExpressionEvaluator.Factory in; - private final StGeohash.GeoHashBoundedGrid bounds; + private final Function bounds; public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory in, - StGeohash.GeoHashBoundedGrid bounds) { + Function bounds) { this.source = source; this.in = in; this.bounds = bounds; @@ -108,12 +109,12 @@ public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory in, @Override public StGeohashFromFieldAndLiteralAndLiteralEvaluator get(DriverContext context) { - return new StGeohashFromFieldAndLiteralAndLiteralEvaluator(source, in.get(context), bounds, context); + return new StGeohashFromFieldAndLiteralAndLiteralEvaluator(source, in.get(context), bounds.apply(context), context); } @Override public String toString() { - return "StGeohashFromFieldAndLiteralAndLiteralEvaluator[" + "in=" + in + ", bounds=" + bounds + "]"; + return "StGeohashFromFieldAndLiteralAndLiteralEvaluator[" + "in=" + in + "]"; } } } diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohashFromFieldDocValuesAndLiteralAndLiteralEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohashFromFieldDocValuesAndLiteralAndLiteralEvaluator.java index 862b2b90c9b2d..35febe6a846d1 100644 --- a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohashFromFieldDocValuesAndLiteralAndLiteralEvaluator.java +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohashFromFieldDocValuesAndLiteralAndLiteralEvaluator.java @@ -7,6 +7,7 @@ import java.lang.IllegalArgumentException; import java.lang.Override; import java.lang.String; +import java.util.function.Function; import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.Page; @@ -71,7 +72,7 @@ public LongBlock eval(int positionCount, LongBlock encodedBlock) { @Override public String toString() { - return "StGeohashFromFieldDocValuesAndLiteralAndLiteralEvaluator[" + "encoded=" + encoded + ", bounds=" + bounds + "]"; + return "StGeohashFromFieldDocValuesAndLiteralAndLiteralEvaluator[" + "encoded=" + encoded + "]"; } @Override @@ -96,10 +97,10 @@ static class Factory implements EvalOperator.ExpressionEvaluator.Factory { private final EvalOperator.ExpressionEvaluator.Factory encoded; - private final StGeohash.GeoHashBoundedGrid bounds; + private final Function bounds; public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory encoded, - StGeohash.GeoHashBoundedGrid bounds) { + Function bounds) { this.source = source; this.encoded = encoded; this.bounds = bounds; @@ -107,12 +108,12 @@ public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory encoded, @Override public StGeohashFromFieldDocValuesAndLiteralAndLiteralEvaluator get(DriverContext context) { - return new StGeohashFromFieldDocValuesAndLiteralAndLiteralEvaluator(source, encoded.get(context), bounds, context); + return new StGeohashFromFieldDocValuesAndLiteralAndLiteralEvaluator(source, encoded.get(context), bounds.apply(context), context); } @Override public String toString() { - return "StGeohashFromFieldDocValuesAndLiteralAndLiteralEvaluator[" + "encoded=" + encoded + ", bounds=" + bounds + "]"; + return "StGeohashFromFieldDocValuesAndLiteralAndLiteralEvaluator[" + "encoded=" + encoded + "]"; } } } diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohexFromFieldAndLiteralAndLiteralEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohexFromFieldAndLiteralAndLiteralEvaluator.java index 24e070c46adda..9d7fd6913c058 100644 --- a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohexFromFieldAndLiteralAndLiteralEvaluator.java +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohexFromFieldAndLiteralAndLiteralEvaluator.java @@ -7,6 +7,7 @@ import java.lang.IllegalArgumentException; import java.lang.Override; import java.lang.String; +import java.util.function.Function; import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.BytesRefBlock; import org.elasticsearch.compute.data.LongBlock; @@ -72,7 +73,7 @@ public LongBlock eval(int positionCount, BytesRefBlock inBlock) { @Override public String toString() { - return "StGeohexFromFieldAndLiteralAndLiteralEvaluator[" + "in=" + in + ", bounds=" + bounds + "]"; + return "StGeohexFromFieldAndLiteralAndLiteralEvaluator[" + "in=" + in + "]"; } @Override @@ -97,10 +98,10 @@ static class Factory implements EvalOperator.ExpressionEvaluator.Factory { private final EvalOperator.ExpressionEvaluator.Factory in; - private final StGeohex.GeoHexBoundedGrid bounds; + private final Function bounds; public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory in, - StGeohex.GeoHexBoundedGrid bounds) { + Function bounds) { this.source = source; this.in = in; this.bounds = bounds; @@ -108,12 +109,12 @@ public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory in, @Override public StGeohexFromFieldAndLiteralAndLiteralEvaluator get(DriverContext context) { - return new StGeohexFromFieldAndLiteralAndLiteralEvaluator(source, in.get(context), bounds, context); + return new StGeohexFromFieldAndLiteralAndLiteralEvaluator(source, in.get(context), bounds.apply(context), context); } @Override public String toString() { - return "StGeohexFromFieldAndLiteralAndLiteralEvaluator[" + "in=" + in + ", bounds=" + bounds + "]"; + return "StGeohexFromFieldAndLiteralAndLiteralEvaluator[" + "in=" + in + "]"; } } } diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohexFromFieldDocValuesAndLiteralAndLiteralEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohexFromFieldDocValuesAndLiteralAndLiteralEvaluator.java index 0ac32cbdbedad..2a37c301bbed4 100644 --- a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohexFromFieldDocValuesAndLiteralAndLiteralEvaluator.java +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohexFromFieldDocValuesAndLiteralAndLiteralEvaluator.java @@ -7,6 +7,7 @@ import java.lang.IllegalArgumentException; import java.lang.Override; import java.lang.String; +import java.util.function.Function; import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.Page; @@ -71,7 +72,7 @@ public LongBlock eval(int positionCount, LongBlock encodedBlock) { @Override public String toString() { - return "StGeohexFromFieldDocValuesAndLiteralAndLiteralEvaluator[" + "encoded=" + encoded + ", bounds=" + bounds + "]"; + return "StGeohexFromFieldDocValuesAndLiteralAndLiteralEvaluator[" + "encoded=" + encoded + "]"; } @Override @@ -96,10 +97,10 @@ static class Factory implements EvalOperator.ExpressionEvaluator.Factory { private final EvalOperator.ExpressionEvaluator.Factory encoded; - private final StGeohex.GeoHexBoundedGrid bounds; + private final Function bounds; public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory encoded, - StGeohex.GeoHexBoundedGrid bounds) { + Function bounds) { this.source = source; this.encoded = encoded; this.bounds = bounds; @@ -107,12 +108,12 @@ public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory encoded, @Override public StGeohexFromFieldDocValuesAndLiteralAndLiteralEvaluator get(DriverContext context) { - return new StGeohexFromFieldDocValuesAndLiteralAndLiteralEvaluator(source, encoded.get(context), bounds, context); + return new StGeohexFromFieldDocValuesAndLiteralAndLiteralEvaluator(source, encoded.get(context), bounds.apply(context), context); } @Override public String toString() { - return "StGeohexFromFieldDocValuesAndLiteralAndLiteralEvaluator[" + "encoded=" + encoded + ", bounds=" + bounds + "]"; + return "StGeohexFromFieldDocValuesAndLiteralAndLiteralEvaluator[" + "encoded=" + encoded + "]"; } } } diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeotileFromFieldAndLiteralAndLiteralEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeotileFromFieldAndLiteralAndLiteralEvaluator.java index 13a8da4ad0c9c..4e8fe75c80d9d 100644 --- a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeotileFromFieldAndLiteralAndLiteralEvaluator.java +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeotileFromFieldAndLiteralAndLiteralEvaluator.java @@ -7,6 +7,7 @@ import java.lang.IllegalArgumentException; import java.lang.Override; import java.lang.String; +import java.util.function.Function; import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.BytesRefBlock; import org.elasticsearch.compute.data.LongBlock; @@ -72,7 +73,7 @@ public LongBlock eval(int positionCount, BytesRefBlock inBlock) { @Override public String toString() { - return "StGeotileFromFieldAndLiteralAndLiteralEvaluator[" + "in=" + in + ", bounds=" + bounds + "]"; + return "StGeotileFromFieldAndLiteralAndLiteralEvaluator[" + "in=" + in + "]"; } @Override @@ -97,10 +98,10 @@ static class Factory implements EvalOperator.ExpressionEvaluator.Factory { private final EvalOperator.ExpressionEvaluator.Factory in; - private final StGeotile.GeoTileBoundedGrid bounds; + private final Function bounds; public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory in, - StGeotile.GeoTileBoundedGrid bounds) { + Function bounds) { this.source = source; this.in = in; this.bounds = bounds; @@ -108,12 +109,12 @@ public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory in, @Override public StGeotileFromFieldAndLiteralAndLiteralEvaluator get(DriverContext context) { - return new StGeotileFromFieldAndLiteralAndLiteralEvaluator(source, in.get(context), bounds, context); + return new StGeotileFromFieldAndLiteralAndLiteralEvaluator(source, in.get(context), bounds.apply(context), context); } @Override public String toString() { - return "StGeotileFromFieldAndLiteralAndLiteralEvaluator[" + "in=" + in + ", bounds=" + bounds + "]"; + return "StGeotileFromFieldAndLiteralAndLiteralEvaluator[" + "in=" + in + "]"; } } } diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeotileFromFieldDocValuesAndLiteralAndLiteralEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeotileFromFieldDocValuesAndLiteralAndLiteralEvaluator.java index c6dc1d23b9f3d..b6951f2ab47a3 100644 --- a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeotileFromFieldDocValuesAndLiteralAndLiteralEvaluator.java +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeotileFromFieldDocValuesAndLiteralAndLiteralEvaluator.java @@ -7,6 +7,7 @@ import java.lang.IllegalArgumentException; import java.lang.Override; import java.lang.String; +import java.util.function.Function; import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.Page; @@ -71,7 +72,7 @@ public LongBlock eval(int positionCount, LongBlock encodedBlock) { @Override public String toString() { - return "StGeotileFromFieldDocValuesAndLiteralAndLiteralEvaluator[" + "encoded=" + encoded + ", bounds=" + bounds + "]"; + return "StGeotileFromFieldDocValuesAndLiteralAndLiteralEvaluator[" + "encoded=" + encoded + "]"; } @Override @@ -96,10 +97,10 @@ static class Factory implements EvalOperator.ExpressionEvaluator.Factory { private final EvalOperator.ExpressionEvaluator.Factory encoded; - private final StGeotile.GeoTileBoundedGrid bounds; + private final Function bounds; public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory encoded, - StGeotile.GeoTileBoundedGrid bounds) { + Function bounds) { this.source = source; this.encoded = encoded; this.bounds = bounds; @@ -107,12 +108,12 @@ public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory encoded, @Override public StGeotileFromFieldDocValuesAndLiteralAndLiteralEvaluator get(DriverContext context) { - return new StGeotileFromFieldDocValuesAndLiteralAndLiteralEvaluator(source, encoded.get(context), bounds, context); + return new StGeotileFromFieldDocValuesAndLiteralAndLiteralEvaluator(source, encoded.get(context), bounds.apply(context), context); } @Override public String toString() { - return "StGeotileFromFieldDocValuesAndLiteralAndLiteralEvaluator[" + "encoded=" + encoded + ", bounds=" + bounds + "]"; + return "StGeotileFromFieldDocValuesAndLiteralAndLiteralEvaluator[" + "encoded=" + encoded + "]"; } } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/GeoHexBoundedPredicate.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/GeoHexBoundedPredicate.java index c0d8b25e07860..368e2e37423ec 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/GeoHexBoundedPredicate.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/GeoHexBoundedPredicate.java @@ -17,20 +17,15 @@ public class GeoHexBoundedPredicate { private final boolean crossesDateline; - private final GeoBoundingBox bbox; + private final GeoBoundingBox bbox, scratch; GeoHexBoundedPredicate(GeoBoundingBox bbox) { this.crossesDateline = bbox.right() < bbox.left(); - // TODO remove this once we get serverless flaky tests to pass - // assert this.crossesDateline == false; this.bbox = bbox; + this.scratch = new GeoBoundingBox(new org.elasticsearch.common.geo.GeoPoint(), new org.elasticsearch.common.geo.GeoPoint()); } public boolean validHex(long hex) { - GeoBoundingBox scratch = new GeoBoundingBox( - new org.elasticsearch.common.geo.GeoPoint(), - new org.elasticsearch.common.geo.GeoPoint() - ); H3SphericalUtil.computeGeoBounds(hex, scratch); if (bbox.top() > scratch.bottom() && bbox.bottom() < scratch.top()) { if (scratch.left() > scratch.right()) { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohash.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohash.java index b4a302824c924..c152108c67649 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohash.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohash.java @@ -15,6 +15,7 @@ import org.elasticsearch.compute.ann.Fixed; import org.elasticsearch.compute.data.BytesRefBlock; import org.elasticsearch.compute.data.LongBlock; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.geometry.Point; import org.elasticsearch.geometry.utils.Geohash; @@ -33,6 +34,7 @@ import java.io.IOException; +import static org.elasticsearch.compute.ann.Fixed.Scope.THREAD_LOCAL; import static org.elasticsearch.xpack.esql.core.type.DataType.LONG; import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.GEO; @@ -52,12 +54,10 @@ public class StGeohash extends SpatialGridFunction implements EvaluatorMapper { */ protected static class GeoHashBoundedGrid implements BoundedGrid { private final int precision; - private final GeoBoundingBox bbox; private final GeoHashBoundedPredicate bounds; - public GeoHashBoundedGrid(int precision, GeoBoundingBox bbox) { + private GeoHashBoundedGrid(int precision, GeoBoundingBox bbox) { this.precision = checkPrecisionRange(precision); - this.bbox = bbox; this.bounds = new GeoHashBoundedPredicate(precision, bbox); } @@ -66,7 +66,8 @@ public long calculateGridId(Point point) { if (bounds.validHash(geohash)) { return Geohash.longEncode(geohash); } - // TODO: Are negative values allowed in geohash long encoding? + // Geohash uses the lowest 4 bites for precision, and if all four are set, we get 15, which is invalid since the + // max precision allowed is 12. This allows us to return -1 to indicate invalid geohashes (since all bits are set to 1). return -1; } @@ -75,9 +76,18 @@ public int precision() { return precision; } - @Override - public String toString() { - return "[" + bbox + "]"; + protected static class Factory { + private final int precision; + private final GeoBoundingBox bbox; + + Factory(int precision, GeoBoundingBox bbox) { + this.precision = checkPrecisionRange(precision); + this.bbox = bbox; + } + + public GeoHashBoundedGrid get(DriverContext context) { + return new GeoHashBoundedGrid(precision, bbox); + } } } @@ -175,10 +185,14 @@ public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvalua } GeoBoundingBox bbox = asGeoBoundingBox(bounds.fold(toEvaluator.foldCtx())); int precision = (int) parameter.fold(toEvaluator.foldCtx()); - GeoHashBoundedGrid bounds = new GeoHashBoundedGrid(precision, bbox); + GeoHashBoundedGrid.Factory bounds = new GeoHashBoundedGrid.Factory(precision, bbox); return spatialDocsValues - ? new StGeohashFromFieldDocValuesAndLiteralAndLiteralEvaluator.Factory(source(), toEvaluator.apply(spatialField()), bounds) - : new StGeohashFromFieldAndLiteralAndLiteralEvaluator.Factory(source(), toEvaluator.apply(spatialField), bounds); + ? new StGeohashFromFieldDocValuesAndLiteralAndLiteralEvaluator.Factory( + source(), + toEvaluator.apply(spatialField()), + bounds::get + ) + : new StGeohashFromFieldAndLiteralAndLiteralEvaluator.Factory(source(), toEvaluator.apply(spatialField), bounds::get); } else { int precision = checkPrecisionRange((int) parameter.fold(toEvaluator.foldCtx())); return spatialDocsValues @@ -196,7 +210,8 @@ public Object fold(FoldContext ctx) { } else { GeoBoundingBox bbox = asGeoBoundingBox(bounds().fold(ctx)); GeoHashBoundedGrid bounds = new GeoHashBoundedGrid(precision, bbox); - return bounds.calculateGridId(GEO.wkbAsPoint(point)); + long gridId = bounds.calculateGridId(GEO.wkbAsPoint(point)); + return gridId < 0 ? null : gridId; } } @@ -211,7 +226,12 @@ static void fromFieldDocValuesAndLiteral(LongBlock.Builder results, int p, LongB } @Evaluator(extraName = "FromFieldAndLiteralAndLiteral", warnExceptions = { IllegalArgumentException.class }) - static void fromFieldAndLiteralAndLiteral(LongBlock.Builder results, int p, BytesRefBlock in, @Fixed GeoHashBoundedGrid bounds) { + static void fromFieldAndLiteralAndLiteral( + LongBlock.Builder results, + int p, + BytesRefBlock in, + @Fixed(includeInToString = false, scope = THREAD_LOCAL) GeoHashBoundedGrid bounds + ) { fromWKB(results, p, in, bounds); } @@ -220,7 +240,7 @@ static void fromFieldDocValuesAndLiteralAndLiteral( LongBlock.Builder results, int p, LongBlock encoded, - @Fixed GeoHashBoundedGrid bounds + @Fixed(includeInToString = false, scope = THREAD_LOCAL) GeoHashBoundedGrid bounds ) { fromEncodedLong(results, p, encoded, bounds); } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohex.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohex.java index 23ca277f4b3b6..936b22ba03ccf 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohex.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohex.java @@ -15,6 +15,7 @@ import org.elasticsearch.compute.ann.Fixed; import org.elasticsearch.compute.data.BytesRefBlock; import org.elasticsearch.compute.data.LongBlock; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.geometry.Point; import org.elasticsearch.h3.H3; @@ -34,6 +35,7 @@ import java.io.IOException; +import static org.elasticsearch.compute.ann.Fixed.Scope.THREAD_LOCAL; import static org.elasticsearch.xpack.esql.core.type.DataType.LONG; import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.GEO; @@ -49,12 +51,10 @@ public class StGeohex extends SpatialGridFunction implements EvaluatorMapper { */ protected static class GeoHexBoundedGrid implements BoundedGrid { private final int precision; - private final GeoBoundingBox bbox; private final GeoHexBoundedPredicate bounds; - public GeoHexBoundedGrid(int precision, GeoBoundingBox bbox) { + private GeoHexBoundedGrid(int precision, GeoBoundingBox bbox) { this.precision = checkPrecisionRange(precision); - this.bbox = bbox; this.bounds = new GeoHexBoundedPredicate(bbox); } @@ -64,7 +64,7 @@ public long calculateGridId(Point point) { if (bounds.validHex(geohex)) { return geohex; } - // TODO: Are we sure negative numbers are not valid + // H3 explicitly requires the highest bit to be zero, freeing up all negative numbers as invalid ids. See H3.isValidHex() return -1L; } @@ -73,9 +73,18 @@ public int precision() { return precision; } - @Override - public String toString() { - return "[" + bbox + "]"; + protected static class Factory { + private final int precision; + private final GeoBoundingBox bbox; + + Factory(int precision, GeoBoundingBox bbox) { + this.precision = checkPrecisionRange(precision); + this.bbox = bbox; + } + + public GeoHexBoundedGrid get(DriverContext context) { + return new GeoHexBoundedGrid(precision, bbox); + } } } @@ -178,10 +187,14 @@ public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvalua } GeoBoundingBox bbox = asGeoBoundingBox(bounds.fold(toEvaluator.foldCtx())); int precision = (int) parameter.fold(toEvaluator.foldCtx()); - GeoHexBoundedGrid bounds = new GeoHexBoundedGrid(precision, bbox); + GeoHexBoundedGrid.Factory bounds = new GeoHexBoundedGrid.Factory(precision, bbox); return spatialDocsValues - ? new StGeohexFromFieldDocValuesAndLiteralAndLiteralEvaluator.Factory(source(), toEvaluator.apply(spatialField()), bounds) - : new StGeohexFromFieldAndLiteralAndLiteralEvaluator.Factory(source(), toEvaluator.apply(spatialField), bounds); + ? new StGeohexFromFieldDocValuesAndLiteralAndLiteralEvaluator.Factory( + source(), + toEvaluator.apply(spatialField()), + bounds::get + ) + : new StGeohexFromFieldAndLiteralAndLiteralEvaluator.Factory(source(), toEvaluator.apply(spatialField), bounds::get); } else { int precision = checkPrecisionRange((int) parameter.fold(toEvaluator.foldCtx())); return spatialDocsValues @@ -199,7 +212,8 @@ public Object fold(FoldContext ctx) { } else { GeoBoundingBox bbox = asGeoBoundingBox(bounds().fold(ctx)); GeoHexBoundedGrid bounds = new GeoHexBoundedGrid(precision, bbox); - return bounds.calculateGridId(GEO.wkbAsPoint(point)); + long gridId = bounds.calculateGridId(GEO.wkbAsPoint(point)); + return gridId < 0 ? null : gridId; } } @@ -214,7 +228,12 @@ static void fromFieldDocValuesAndLiteral(LongBlock.Builder results, int p, LongB } @Evaluator(extraName = "FromFieldAndLiteralAndLiteral", warnExceptions = { IllegalArgumentException.class }) - static void fromFieldAndLiteralAndLiteral(LongBlock.Builder results, int p, BytesRefBlock in, @Fixed GeoHexBoundedGrid bounds) { + static void fromFieldAndLiteralAndLiteral( + LongBlock.Builder results, + int p, + BytesRefBlock in, + @Fixed(includeInToString = false, scope = THREAD_LOCAL) GeoHexBoundedGrid bounds + ) { fromWKB(results, p, in, bounds); } @@ -223,7 +242,7 @@ static void fromFieldDocValuesAndLiteralAndLiteral( LongBlock.Builder results, int p, LongBlock encoded, - @Fixed GeoHexBoundedGrid bounds + @Fixed(includeInToString = false, scope = THREAD_LOCAL) GeoHexBoundedGrid bounds ) { fromEncodedLong(results, p, encoded, bounds); } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeotile.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeotile.java index 81b4e676ceafd..86a9e2bbc6030 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeotile.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeotile.java @@ -15,6 +15,7 @@ import org.elasticsearch.compute.ann.Fixed; import org.elasticsearch.compute.data.BytesRefBlock; import org.elasticsearch.compute.data.LongBlock; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.geometry.Point; import org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileBoundedPredicate; @@ -33,6 +34,7 @@ import java.io.IOException; +import static org.elasticsearch.compute.ann.Fixed.Scope.THREAD_LOCAL; import static org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileUtils.checkPrecisionRange; import static org.elasticsearch.xpack.esql.core.type.DataType.LONG; import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.GEO; @@ -53,12 +55,10 @@ public class StGeotile extends SpatialGridFunction implements EvaluatorMapper { */ protected static class GeoTileBoundedGrid implements BoundedGrid { private final int precision; - private final GeoBoundingBox bbox; private final GeoTileBoundedPredicate bounds; - public GeoTileBoundedGrid(int precision, GeoBoundingBox bbox) { + private GeoTileBoundedGrid(int precision, GeoBoundingBox bbox) { this.precision = checkPrecisionRange(precision); - this.bbox = bbox; this.bounds = new GeoTileBoundedPredicate(precision, bbox); } @@ -69,7 +69,8 @@ public long calculateGridId(Point point) { if (bounds.validTile(x, y, precision)) { return GeoTileUtils.longEncodeTiles(precision, x, y); } - // TODO: Are we sure negative numbers are not valid + // GeoTileUtils uses the highest 6 bits to store the zoom level. However, MAX_ZOOM is 29, which takes 5 bits. + // This leaves the sign bit unused, so it can be used to indicate an invalid tile. return -1L; } @@ -78,9 +79,18 @@ public int precision() { return precision; } - @Override - public String toString() { - return "[" + bbox + "]"; + protected static class Factory { + private final int precision; + private final GeoBoundingBox bbox; + + Factory(int precision, GeoBoundingBox bbox) { + this.precision = checkPrecisionRange(precision); + this.bbox = bbox; + } + + public GeoTileBoundedGrid get(DriverContext context) { + return new GeoTileBoundedGrid(precision, bbox); + } } } @@ -169,10 +179,14 @@ public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvalua } GeoBoundingBox bbox = asGeoBoundingBox(bounds.fold(toEvaluator.foldCtx())); int precision = (int) parameter.fold(toEvaluator.foldCtx()); - GeoTileBoundedGrid bounds = new GeoTileBoundedGrid(precision, bbox); + GeoTileBoundedGrid.Factory bounds = new GeoTileBoundedGrid.Factory(precision, bbox); return spatialDocsValues - ? new StGeotileFromFieldDocValuesAndLiteralAndLiteralEvaluator.Factory(source(), toEvaluator.apply(spatialField()), bounds) - : new StGeotileFromFieldAndLiteralAndLiteralEvaluator.Factory(source(), toEvaluator.apply(spatialField), bounds); + ? new StGeotileFromFieldDocValuesAndLiteralAndLiteralEvaluator.Factory( + source(), + toEvaluator.apply(spatialField()), + bounds::get + ) + : new StGeotileFromFieldAndLiteralAndLiteralEvaluator.Factory(source(), toEvaluator.apply(spatialField), bounds::get); } else { int precision = checkPrecisionRange((int) parameter.fold(toEvaluator.foldCtx())); return spatialDocsValues @@ -190,7 +204,8 @@ public Object fold(FoldContext ctx) { } else { GeoBoundingBox bbox = asGeoBoundingBox(bounds().fold(ctx)); GeoTileBoundedGrid bounds = new GeoTileBoundedGrid(precision, bbox); - return bounds.calculateGridId(GEO.wkbAsPoint(point)); + long gridId = bounds.calculateGridId(GEO.wkbAsPoint(point)); + return gridId < 0 ? null : gridId; } } @@ -205,7 +220,12 @@ static void fromFieldDocValuesAndLiteral(LongBlock.Builder results, int p, LongB } @Evaluator(extraName = "FromFieldAndLiteralAndLiteral", warnExceptions = { IllegalArgumentException.class }) - static void fromFieldAndLiteralAndLiteral(LongBlock.Builder results, int p, BytesRefBlock in, @Fixed GeoTileBoundedGrid bounds) { + static void fromFieldAndLiteralAndLiteral( + LongBlock.Builder results, + int p, + BytesRefBlock in, + @Fixed(includeInToString = false, scope = THREAD_LOCAL) GeoTileBoundedGrid bounds + ) { fromWKB(results, p, in, bounds); } @@ -214,7 +234,7 @@ static void fromFieldDocValuesAndLiteralAndLiteral( LongBlock.Builder results, int p, LongBlock encoded, - @Fixed GeoTileBoundedGrid bounds + @Fixed(includeInToString = false, scope = THREAD_LOCAL) GeoTileBoundedGrid bounds ) { fromEncodedLong(results, p, encoded, bounds); } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/SpatialGridFunctionTestCase.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/SpatialGridFunctionTestCase.java index 328e166ede5e9..0d4eaf6e826d9 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/SpatialGridFunctionTestCase.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/SpatialGridFunctionTestCase.java @@ -102,21 +102,17 @@ protected static void addTestCaseSuppliers( BytesRef geometry = (BytesRef) geoTypedData.data(); int precision = between(1, 8); TestCaseSupplier.TypedData precisionData = new TestCaseSupplier.TypedData(precision, INTEGER, "precision"); - Rectangle bounds = new Rectangle(-180, 180, 90, -90); String evaluatorName = "FromFieldAndLiteralAndLiteralEvaluator[in=Attribute[channel=0], bounds=["; if (literalPrecision) { precisionData = precisionData.forceLiteral(); - evaluatorName = "FromFieldAndLiteralAndLiteralEvaluator[in=Attribute[channel=0], bounds=["; + evaluatorName = "FromFieldAndLiteralAndLiteralEvaluator[in=Attribute[channel=0]"; } - TestCaseSupplier.TypedData boundsData; - // Create a rectangle as bounds - BytesRef boundsBytesRef = GEO.asWkb(bounds); - boundsData = new TestCaseSupplier.TypedData(boundsBytesRef, GEO_SHAPE, "bounds").forceLiteral(); + var boundsData = randomBoundsData(); return new TestCaseSupplier.TestCase( - List.of(geoTypedData, precisionData, boundsData), + List.of(geoTypedData, precisionData, boundsData.typedData), startsWith(getFunctionClassName() + evaluatorName), LONG, - equalTo(expectedValueWithBounds.apply(geometry, precision, SpatialGridFunction.asGeoBoundingBox(bounds))) + equalTo(expectedValueWithBounds.apply(geometry, precision, boundsData.geoBoundingBox())) ); })); } @@ -141,6 +137,25 @@ public static TestCaseSupplier.TypedDataSupplier testCaseSupplier(DataType dataT } } + protected record TestBoundsData(GeoBoundingBox geoBoundingBox, TestCaseSupplier.TypedData typedData) {} + + private static TestBoundsData randomBoundsData() { + // Create a rectangle with random center and random size, that does not exceed geographic bounds + double x = randomDoubleBetween(-180.1, 179.1, false); + double y = randomDoubleBetween(-90.1, 89.1, false); + double width = randomDoubleBetween(0.1, 180.0, true); + double height = randomDoubleBetween(0.1, 90.0, true); + double minX = Math.max(-180, x - width / 2); + double maxX = Math.min(180, x + width / 2); + double minY = Math.max(-90, y - height / 2); + double maxY = Math.min(90, y + height / 2); + Rectangle bounds = new Rectangle(minX, maxX, maxY, minY); + return new TestBoundsData( + SpatialGridFunction.asGeoBoundingBox(bounds), + new TestCaseSupplier.TypedData(GEO.asWkb(bounds), GEO_SHAPE, "bounds").forceLiteral() + ); + } + protected Long process(int precision, BiFunction expectedValue) { Object spatialObj = this.testCase.getDataValues().getFirst(); assumeNotNull(spatialObj); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohashTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohashTests.java index 625f23f4c9a3c..185591df91b4d 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohashTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohashTests.java @@ -50,9 +50,10 @@ private static long valueOf(BytesRef wkb, int precision) { return StGeohash.unboundedGrid.calculateGridId(UNSPECIFIED.wkbAsPoint(wkb), precision); } - private static long boundedValueOf(BytesRef wkb, int precision, GeoBoundingBox bbox) { - StGeohash.GeoHashBoundedGrid bounds = new StGeohash.GeoHashBoundedGrid(precision, bbox); - return bounds.calculateGridId(UNSPECIFIED.wkbAsPoint(wkb)); + private static Long boundedValueOf(BytesRef wkb, int precision, GeoBoundingBox bbox) { + StGeohash.GeoHashBoundedGrid bounds = new StGeohash.GeoHashBoundedGrid.Factory(precision, bbox).get(null); + long gridId = bounds.calculateGridId(UNSPECIFIED.wkbAsPoint(wkb)); + return gridId < 0 ? null : gridId; } @Override diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohexTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohexTests.java index e48b65df050af..f52241806a3be 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohexTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeohexTests.java @@ -50,9 +50,10 @@ private static long valueOf(BytesRef wkb, int precision) { return StGeohex.unboundedGrid.calculateGridId(UNSPECIFIED.wkbAsPoint(wkb), precision); } - private static long boundedValueOf(BytesRef wkb, int precision, GeoBoundingBox bbox) { - StGeohex.GeoHexBoundedGrid bounds = new StGeohex.GeoHexBoundedGrid(precision, bbox); - return bounds.calculateGridId(UNSPECIFIED.wkbAsPoint(wkb)); + private static Long boundedValueOf(BytesRef wkb, int precision, GeoBoundingBox bbox) { + StGeohex.GeoHexBoundedGrid bounds = new StGeohex.GeoHexBoundedGrid.Factory(precision, bbox).get(null); + long gridId = bounds.calculateGridId(UNSPECIFIED.wkbAsPoint(wkb)); + return gridId < 0 ? null : gridId; } @Override diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeotileTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeotileTests.java index 01632fb07bf38..5661f1eacf533 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeotileTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StGeotileTests.java @@ -13,7 +13,6 @@ import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.geo.GeoBoundingBox; import org.elasticsearch.license.License; -import org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileUtils; import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; @@ -23,6 +22,7 @@ import java.util.List; import java.util.function.Supplier; +import static org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileUtils.MAX_ZOOM; import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.UNSPECIFIED; import static org.hamcrest.Matchers.containsString; @@ -50,9 +50,10 @@ private static long valueOf(BytesRef wkb, int precision) { return StGeotile.unboundedGrid.calculateGridId(UNSPECIFIED.wkbAsPoint(wkb), precision); } - private static long boundedValueOf(BytesRef wkb, int precision, GeoBoundingBox bbox) { - StGeotile.GeoTileBoundedGrid bounds = new StGeotile.GeoTileBoundedGrid(precision, bbox); - return bounds.calculateGridId(UNSPECIFIED.wkbAsPoint(wkb)); + private static Long boundedValueOf(BytesRef wkb, int precision, GeoBoundingBox bbox) { + StGeotile.GeoTileBoundedGrid bounds = new StGeotile.GeoTileBoundedGrid.Factory(precision, bbox).get(null); + long gridId = bounds.calculateGridId(UNSPECIFIED.wkbAsPoint(wkb)); + return gridId < 0 ? null : gridId; } @Override @@ -64,7 +65,7 @@ protected Expression build(Source source, List args) { public void testInvalidPrecision() { IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () -> process(-1, StGeotileTests::valueOf)); assertThat(ex.getMessage(), containsString("Invalid geotile_grid precision of -1. Must be between 0 and 29.")); - ex = expectThrows(IllegalArgumentException.class, () -> process(GeoTileUtils.MAX_ZOOM + 1, StGeotileTests::valueOf)); + ex = expectThrows(IllegalArgumentException.class, () -> process(MAX_ZOOM + 1, StGeotileTests::valueOf)); assertThat(ex.getMessage(), containsString("Invalid geotile_grid precision of 30. Must be between 0 and 29.")); } }