2727
2828import java .io .IOException ;
2929import java .util .Objects ;
30+ import java .util .function .Predicate ;
3031
3132import static org .elasticsearch .xpack .esql .core .expression .TypeResolutions .ParamOrdinal .FIRST ;
3233import static org .elasticsearch .xpack .esql .core .expression .TypeResolutions .ParamOrdinal .SECOND ;
@@ -55,15 +56,17 @@ protected BinarySpatialFunction(
5556 Expression right ,
5657 boolean leftDocValues ,
5758 boolean rightDocValues ,
58- boolean pointsOnly
59+ boolean pointsOnly ,
60+ boolean supportsGrid
5961 ) {
6062 super (source , left , right );
6163 this .leftDocValues = leftDocValues ;
6264 this .rightDocValues = rightDocValues ;
63- this .spatialTypeResolver = new SpatialTypeResolver (this , pointsOnly );
65+ this .spatialTypeResolver = new SpatialTypeResolver (this , pointsOnly , supportsGrid );
6466 }
6567
66- protected BinarySpatialFunction (StreamInput in , boolean leftDocValues , boolean rightDocValues , boolean pointsOnly ) throws IOException {
68+ protected BinarySpatialFunction (StreamInput in , boolean leftDocValues , boolean rightDocValues , boolean pointsOnly , boolean supportsGrid )
69+ throws IOException {
6770 // The doc-values fields are only used on data nodes local planning, and therefor never serialized
6871 this (
6972 in .getTransportVersion ().onOrAfter (TransportVersions .ESQL_SERIALIZE_SOURCE_FUNCTIONS_WARNINGS )
@@ -73,7 +76,8 @@ protected BinarySpatialFunction(StreamInput in, boolean leftDocValues, boolean r
7376 in .readNamedWriteable (Expression .class ),
7477 leftDocValues ,
7578 rightDocValues ,
76- pointsOnly
79+ pointsOnly ,
80+ supportsGrid
7781 );
7882 }
7983
@@ -119,10 +123,12 @@ protected TypeResolution resolveType() {
119123 static class SpatialTypeResolver {
120124 private final SpatialEvaluatorFactory .SpatialSourceResolution supplier ;
121125 private final boolean pointsOnly ;
126+ private final boolean supportsGrid ;
122127
123- SpatialTypeResolver (SpatialEvaluatorFactory .SpatialSourceResolution supplier , boolean pointsOnly ) {
128+ SpatialTypeResolver (SpatialEvaluatorFactory .SpatialSourceResolution supplier , boolean pointsOnly , boolean supportsGrid ) {
124129 this .supplier = supplier ;
125130 this .pointsOnly = pointsOnly ;
131+ this .supportsGrid = supportsGrid ;
126132 }
127133
128134 public Expression left () {
@@ -147,10 +153,16 @@ protected TypeResolution resolveType() {
147153 }
148154 }
149155
150- protected Expression .TypeResolution isSpatial (Expression e , TypeResolutions .ParamOrdinal paramOrd ) {
156+ protected Expression .TypeResolution isCompatibleSpatial (Expression e , TypeResolutions .ParamOrdinal paramOrd ) {
151157 return pointsOnly
152158 ? EsqlTypeResolutions .isSpatialPoint (e , sourceText (), paramOrd )
153- : EsqlTypeResolutions .isSpatial (e , sourceText (), paramOrd );
159+ : (supportsGrid
160+ ? EsqlTypeResolutions .isSpatialOrGrid (e , sourceText (), paramOrd )
161+ : EsqlTypeResolutions .isSpatial (e , sourceText (), paramOrd ));
162+ }
163+
164+ protected Expression .TypeResolution isGeoPoint (Expression e , TypeResolutions .ParamOrdinal paramOrd ) {
165+ return isType (e , GEO_POINT ::equals , sourceText (), paramOrd , GEO_POINT .typeName ());
154166 }
155167
156168 private TypeResolution resolveType (
@@ -159,8 +171,8 @@ private TypeResolution resolveType(
159171 TypeResolutions .ParamOrdinal leftOrdinal ,
160172 TypeResolutions .ParamOrdinal rightOrdinal
161173 ) {
162- TypeResolution leftResolution = isSpatial (leftExpression , leftOrdinal );
163- TypeResolution rightResolution = isSpatial (rightExpression , rightOrdinal );
174+ TypeResolution leftResolution = isCompatibleSpatial (leftExpression , leftOrdinal );
175+ TypeResolution rightResolution = isCompatibleSpatial (rightExpression , rightOrdinal );
164176 if (leftResolution .resolved ()) {
165177 return resolveType (leftExpression , rightExpression , rightOrdinal );
166178 } else if (rightResolution .resolved ()) {
@@ -176,9 +188,16 @@ protected TypeResolution resolveType(
176188 TypeResolutions .ParamOrdinal otherParamOrdinal
177189 ) {
178190 if (isNull (spatialExpression .dataType ())) {
179- return isSpatial (otherExpression , otherParamOrdinal );
191+ return isCompatibleSpatial (otherExpression , otherParamOrdinal );
180192 }
181193 TypeResolution resolution = isSameSpatialType (spatialExpression .dataType (), otherExpression , sourceText (), otherParamOrdinal );
194+ // TODO Remove these grid checks once we support geo_shape relation to geoGrid
195+ if (resolution .resolved () && DataType .isGeoGrid (spatialExpression .dataType ())) {
196+ resolution = isGeoPoint (otherExpression , otherParamOrdinal );
197+ }
198+ if (resolution .resolved () && DataType .isGeoGrid (otherExpression .dataType ())) {
199+ resolution = isGeoPoint (spatialExpression , otherParamOrdinal == FIRST ? SECOND : FIRST );
200+ }
182201 if (resolution .unresolved ()) {
183202 return resolution ;
184203 }
@@ -192,15 +211,12 @@ protected TypeResolution isSameSpatialType(
192211 String operationName ,
193212 TypeResolutions .ParamOrdinal paramOrd
194213 ) {
195- return pointsOnly
196- ? isType (expression , dt -> dt == spatialDataType , operationName , paramOrd , compatibleTypeNames (spatialDataType ))
197- : isType (
198- expression ,
199- dt -> DataType .isSpatial (dt ) && spatialCRSCompatible (spatialDataType , dt ),
200- operationName ,
201- paramOrd ,
202- compatibleTypeNames (spatialDataType )
203- );
214+ Predicate <DataType > isSpatialType = pointsOnly
215+ ? dt -> dt == spatialDataType
216+ : (supportsGrid
217+ ? dt -> DataType .isSpatialOrGrid (dt ) && spatialCRSCompatible (spatialDataType , dt )
218+ : dt -> DataType .isSpatial (dt ) && spatialCRSCompatible (spatialDataType , dt ));
219+ return isType (expression , isSpatialType , operationName , paramOrd , compatibleTypeNames (spatialDataType ));
204220 }
205221 }
206222
@@ -248,7 +264,7 @@ public enum SpatialCrsType {
248264
249265 public static SpatialCrsType fromDataType (DataType dataType ) {
250266 return DataType .isSpatialGeo (dataType ) ? SpatialCrsType .GEO
251- : DataType .isSpatial (dataType ) ? SpatialCrsType .CARTESIAN
267+ : DataType .isSpatialOrGrid (dataType ) ? SpatialCrsType .CARTESIAN
252268 : SpatialCrsType .UNSPECIFIED ;
253269 }
254270 }
@@ -278,8 +294,8 @@ public TranslationAware.Translatable translatable(LucenePushdownPredicates pushd
278294 // The use of foldable here instead of SpatialEvaluatorFieldKey.isConstant is intentional to match the behavior of the
279295 // Lucene pushdown code in EsqlTranslationHandler::SpatialRelatesTranslator
280296 // We could enhance both places to support ReferenceAttributes that refer to constants, but that is a larger change
281- return isPushableSpatialAttribute (left (), pushdownPredicates ) && right (). foldable ( )
282- || isPushableSpatialAttribute (right (), pushdownPredicates ) && left (). foldable ( )
297+ return isPushableSpatialAttribute (left (), pushdownPredicates ) && isPushableLiteralAttribute ( right ())
298+ || isPushableSpatialAttribute (right (), pushdownPredicates ) && isPushableLiteralAttribute ( left ())
283299 ? TranslationAware .Translatable .YES
284300 : TranslationAware .Translatable .NO ;
285301
@@ -288,4 +304,9 @@ public TranslationAware.Translatable translatable(LucenePushdownPredicates pushd
288304 private static boolean isPushableSpatialAttribute (Expression exp , LucenePushdownPredicates p ) {
289305 return exp instanceof FieldAttribute fa && DataType .isSpatial (fa .dataType ()) && fa .getExactInfo ().hasExact () && p .isIndexed (fa );
290306 }
307+
308+ private static boolean isPushableLiteralAttribute (Expression exp ) {
309+ // TODO: Support pushdown of geo-grid queries where the constant is a geo-grid-id literal
310+ return DataType .isSpatial (exp .dataType ()) && exp .foldable ();
311+ }
291312}
0 commit comments