Fix ST_DISTANCE handling of invalid geometry literals that fold to null#140116
Fix ST_DISTANCE handling of invalid geometry literals that fold to null#140116craigtaverner merged 12 commits intoelastic:mainfrom
Conversation
| "Unsupported combination of literal [" + value.getClass().getSimpleName() + "] of type [" + dataType + "]" | ||
| ); | ||
| var objType = (value == null ? "null" : value.getClass().getSimpleName()); | ||
| throw new IllegalArgumentException("Unsupported combination of literal [" + objType + "] of type [" + dataType + "]"); |
There was a problem hiding this comment.
Isn't there a difference between the Java null value, and an object of type null (i.e., ESQL's NULL)? Or is the former the way we represent the latter in this case?
There was a problem hiding this comment.
Yes, the java null should never appear here, which is why I've been unable to reproduce this. I think the actual bug lies elsewhere and is creating this java null during the fold operation, somewhere in another function.
There was a problem hiding this comment.
So it seems my st_distance.fold function was not correctly following null folding rules. We need explicit support for nulls here, and I've changed this code to return null instead of throwing an exception.
There was a problem hiding this comment.
Got to reproduce this with:
ROW x = MV_SLICE(["1"], 0, 0)::geo_point
| EVAL y = st_distance(x, x)
I had to add that MV_SLICE part, that ends up being null, but probably avoids some other null folding rules
There was a problem hiding this comment.
Thanks Ivan, that helped me track down and fix the real bug. It appears that fold needs to explicitly handle nulls. The default implementation of fold in EvaluatorMapper returns a raw java null, and all overriding implementations appear to need to do that too.
db40e5f to
26513fb
Compare
|
Pinging @elastic/es-analytical-engine (Team:Analytics) |
|
Hi @craigtaverner, I've created a changelog YAML for you. |
…icsearch into geometry_literal_npe
|
Hi @craigtaverner, I've updated the changelog YAML for you. |
…icsearch into geometry_literal_npe
| throw new IllegalArgumentException("Failed to fold constant fields: " + e.getMessage(), e); | ||
| } | ||
| protected SpatialRelations getSpatialRelations() { | ||
| return (crsType() == SpatialCrsType.GEO) ? GEO : CARTESIAN; |
There was a problem hiding this comment.
I find this kind of ternary expression easier to read when the condition is within parentheses. But your point is valid, I've been using parenthesis so long I started thinking they were required...
There was a problem hiding this comment.
So I did this anyway, since the expression is so simple.
| return foldGeoGrid(ctx, left(), right(), right().dataType()); | ||
| } | ||
| } | ||
| GeometryDocValueReader docValueReader = asGeometryDocValueReader(ctx, crsType(), left()); |
There was a problem hiding this comment.
FWIW, you could shave a few cycles if you early exit here when docValuesReader is already null (though IIUC folding is done during planning so is probably not on the hot path).
There was a problem hiding this comment.
Yes, not on the hot path. I think the code is simpler with a single if.
| long gridId = (Long) valueOf(ctx, gridExp); | ||
| return GEO.compareGeometryAndGrid(makeGeometryFromLiteral(ctx, spatialExp), gridId, gridType); | ||
| protected SpatialRelations getSpatialRelations() { | ||
| return (crsType() == SpatialCrsType.GEO) ? GEO : CARTESIAN; |
| long gridId = (Long) valueOf(ctx, gridExp); | ||
| return GEO.compareGeometryAndGrid(makeGeometryFromLiteral(ctx, spatialExp), gridId, gridType); | ||
| protected SpatialRelations getSpatialRelations() { | ||
| return (crsType() == SpatialCrsType.GEO) ? GEO : CARTESIAN; |
| } | ||
|
|
||
| /** Spatial relates functions always use Lucene GeometryDocValueReader and Component2D instead of geometries */ | ||
| protected Object fold(Geometry leftGeom, Geometry rightGeom) { |
| throw new IllegalArgumentException("Failed to fold constant fields: " + e.getMessage(), e); | ||
| } | ||
| protected SpatialRelations getSpatialRelations() { | ||
| return (crsType() == SpatialCrsType.GEO) ? GEO : CARTESIAN; |
|
|
||
| @Override | ||
| public Object fold(FoldContext ctx) { | ||
| var leftGeom = makeGeometryFromLiteral(ctx, left()); |
There was a problem hiding this comment.
FWIW, you could shave a few cycles if you early exit here when leftGeom is already null (though IIUC folding is done during planning so is probably not on the hot path).
There was a problem hiding this comment.
I'll stick with a single if for simplicity.
ST_CONTAINS needs a custom version of fold() due to the need to split all components into separate components. We could consider doing the same for all other functions, but that would require larger refactoring and could incut performance costs.
💔 Backport failed
You can use sqren/backport to manually backport by running |
…ll (elastic#140116) Occasionally on serverless we see a NullPointerException in parsing a geometry literal within ST_DISTANCE functions. This turned out to be hard to reproduce since we cannot see the original query causing this problem. It has been reported in elastic#138594, including a full stack track confirming that it occurs during planning, while folding literals. It turned out to be reproducible with a query like: ``` ROW point = MV_SLICE(["POINT(0)"], 0, 0)::geo_point | EVAL distance = ST_DISTANCE(point, point) ``` Here the WKT is invalid, and the fold over the MV_SLICE would return literal `null` values, since the inner TO_GEOPOINT function was throwing exceptions (since the WKT was invalid). It turned out that MvSlice was using EvaluatorMapper.fold for folding the children, which returns literal java `null` values, and any function using these values needs to also handle literal nulls. So, `fold()` is expected to explicitly return null values when input is null, and the implementation of fold in StDistance did not do this. This PR now covers all descendents of BinarySpatialFunction: * ST_DISTANCE * ST_INTERSECTS * ST_DISJOINT * ST_CONTAINS * ST_WITHIN
|
Manual backport in #140265 |
…ll (#140116) (#140265) Occasionally on serverless we see a NullPointerException in parsing a geometry literal within ST_DISTANCE functions. This turned out to be hard to reproduce since we cannot see the original query causing this problem. It has been reported in #138594, including a full stack track confirming that it occurs during planning, while folding literals. It turned out to be reproducible with a query like: ``` ROW point = MV_SLICE(["POINT(0)"], 0, 0)::geo_point | EVAL distance = ST_DISTANCE(point, point) ``` Here the WKT is invalid, and the fold over the MV_SLICE would return literal `null` values, since the inner TO_GEOPOINT function was throwing exceptions (since the WKT was invalid). It turned out that MvSlice was using EvaluatorMapper.fold for folding the children, which returns literal java `null` values, and any function using these values needs to also handle literal nulls. So, `fold()` is expected to explicitly return null values when input is null, and the implementation of fold in StDistance did not do this. This PR now covers all descendents of BinarySpatialFunction: * ST_DISTANCE * ST_INTERSECTS * ST_DISJOINT * ST_CONTAINS * ST_WITHIN
…ll (elastic#140116) (elastic#140265) Occasionally on serverless we see a NullPointerException in parsing a geometry literal within ST_DISTANCE functions. This turned out to be hard to reproduce since we cannot see the original query causing this problem. It has been reported in elastic#138594, including a full stack track confirming that it occurs during planning, while folding literals. It turned out to be reproducible with a query like: ``` ROW point = MV_SLICE(["POINT(0)"], 0, 0)::geo_point | EVAL distance = ST_DISTANCE(point, point) ``` Here the WKT is invalid, and the fold over the MV_SLICE would return literal `null` values, since the inner TO_GEOPOINT function was throwing exceptions (since the WKT was invalid). It turned out that MvSlice was using EvaluatorMapper.fold for folding the children, which returns literal java `null` values, and any function using these values needs to also handle literal nulls. So, `fold()` is expected to explicitly return null values when input is null, and the implementation of fold in StDistance did not do this. This PR now covers all descendents of BinarySpatialFunction: * ST_DISTANCE * ST_INTERSECTS * ST_DISJOINT * ST_CONTAINS * ST_WITHIN
…ll (#140116) (#140265) (#140276) Occasionally on serverless we see a NullPointerException in parsing a geometry literal within ST_DISTANCE functions. This turned out to be hard to reproduce since we cannot see the original query causing this problem. It has been reported in #138594, including a full stack track confirming that it occurs during planning, while folding literals. It turned out to be reproducible with a query like: ``` ROW point = MV_SLICE(["POINT(0)"], 0, 0)::geo_point | EVAL distance = ST_DISTANCE(point, point) ``` Here the WKT is invalid, and the fold over the MV_SLICE would return literal `null` values, since the inner TO_GEOPOINT function was throwing exceptions (since the WKT was invalid). It turned out that MvSlice was using EvaluatorMapper.fold for folding the children, which returns literal java `null` values, and any function using these values needs to also handle literal nulls. So, `fold()` is expected to explicitly return null values when input is null, and the implementation of fold in StDistance did not do this. This PR now covers all descendents of BinarySpatialFunction: * ST_DISTANCE * ST_INTERSECTS * ST_DISJOINT * ST_CONTAINS * ST_WITHIN
…ll (elastic#140116) Occasionally on serverless we see a NullPointerException in parsing a geometry literal within ST_DISTANCE functions. This turned out to be hard to reproduce since we cannot see the original query causing this problem. It has been reported in elastic#138594, including a full stack track confirming that it occurs during planning, while folding literals. It turned out to be reproducible with a query like: ``` ROW point = MV_SLICE(["POINT(0)"], 0, 0)::geo_point | EVAL distance = ST_DISTANCE(point, point) ``` Here the WKT is invalid, and the fold over the MV_SLICE would return literal `null` values, since the inner TO_GEOPOINT function was throwing exceptions (since the WKT was invalid). It turned out that MvSlice was using EvaluatorMapper.fold for folding the children, which returns literal java `null` values, and any function using these values needs to also handle literal nulls. So, `fold()` is expected to explicitly return null values when input is null, and the implementation of fold in StDistance did not do this. This PR now covers all descendents of BinarySpatialFunction: * ST_DISTANCE * ST_INTERSECTS * ST_DISJOINT * ST_CONTAINS * ST_WITHIN
Occasionally on serverless we see a NullPointerException in parsing a geometry literal within ST_DISTANCE functions. This turned out to be hard to reproduce since we cannot see the original query causing this problem. It has been reported in #138594, including a full stack track confirming that it occurs during planning, while folding literals.
It turned out to be reproducible with a query like:
Here the WKT is invalid, and the fold over the MV_SLICE would return literal
nullvalues, since the inner TO_GEOPOINT function was throwing exceptions (since the WKT was invalid). It turned out that MvSlice was using EvaluatorMapper.fold for folding the children, which returns literal javanullvalues, and any function using these values needs to also handle literal nulls. So,fold()is expected to explicitly return null values when input is null, and the implementation of fold in StDistance did not do this.This PR now covers all descendents of BinarySpatialFunction:
Fixes #138594