From 711ad9e970bf9a4180bb16df34d0b7d0a5317b69 Mon Sep 17 00:00:00 2001 From: Akshat Nehra Date: Sat, 13 Jun 2026 03:21:22 +0000 Subject: [PATCH] MDEV-39981 Fix ST_GEOMFROMGEOJSON returning wrong result with reversed key order ST_GEOMFROMGEOJSON requires JSON keys to appear in a specific order (type before geometries/features/geometry/coordinates). When keys appear in a different order, the JSON scanner is left in an inconsistent state because json_skip_level() is not called to advance past the value when the type has not yet been determined. Fix: add json_skip_level() calls in the geometries, features, and geometry key handlers when the type key has not yet been encountered. This mirrors the existing pattern in the coordinates handler. All new code of the whole pull request, including one or several files that are either new files or modified ones, are contributed under the BSD-new license. I am contributing on behalf of my employer Amazon Web Services, Inc. --- mysql-test/main/mdev_39981.result | 48 +++++++++++++++++++++++++++++++ mysql-test/main/mdev_39981.test | 38 ++++++++++++++++++++++++ sql/spatial.cc | 6 ++++ 3 files changed, 92 insertions(+) create mode 100644 mysql-test/main/mdev_39981.result create mode 100644 mysql-test/main/mdev_39981.test diff --git a/mysql-test/main/mdev_39981.result b/mysql-test/main/mdev_39981.result new file mode 100644 index 0000000000000..12a4c67c04cf7 --- /dev/null +++ b/mysql-test/main/mdev_39981.result @@ -0,0 +1,48 @@ +# +# MDEV-39981: ST_GEOMFROMGEOJSON returns wrong result with reversed key order +# +# GeometryCollection: type before geometries (baseline) +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"type":"GeometryCollection","geometries":[]}')); +ST_ASTEXT(ST_GEOMFROMGEOJSON('{"type":"GeometryCollection","geometries":[]}')) +GEOMETRYCOLLECTION EMPTY +# GeometryCollection: geometries before type (empty) +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometries":[],"type":"GeometryCollection"}')); +ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometries":[],"type":"GeometryCollection"}')) +GEOMETRYCOLLECTION EMPTY +# GeometryCollection: geometries before type (non-empty) +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometries":[{"type":"Point","coordinates":[0,1]}],"type":"GeometryCollection"}')); +ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometries":[{"type":"Point","coordinates":[0,1]}],"type":"GeometryCollection"}')) +GEOMETRYCOLLECTION(POINT(0 1)) +# GeometryCollection: multiple geometries before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometries":[{"type":"Point","coordinates":[1,2]},{"type":"Point","coordinates":[3,4]}],"type":"GeometryCollection"}')); +ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometries":[{"type":"Point","coordinates":[1,2]},{"type":"Point","coordinates":[3,4]}],"type":"GeometryCollection"}')) +GEOMETRYCOLLECTION(POINT(1 2),POINT(3 4)) +# Point: coordinates before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"coordinates":[0,1],"type":"Point"}')); +ST_ASTEXT(ST_GEOMFROMGEOJSON('{"coordinates":[0,1],"type":"Point"}')) +POINT(0 1) +# LineString: coordinates before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"coordinates":[[0,0],[1,1],[2,2]],"type":"LineString"}')); +ST_ASTEXT(ST_GEOMFROMGEOJSON('{"coordinates":[[0,0],[1,1],[2,2]],"type":"LineString"}')) +LINESTRING(0 0,1 1,2 2) +# Polygon: coordinates before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"coordinates":[[[0,0],[1,0],[1,1],[0,1],[0,0]]],"type":"Polygon"}')); +ST_ASTEXT(ST_GEOMFROMGEOJSON('{"coordinates":[[[0,0],[1,0],[1,1],[0,1],[0,0]]],"type":"Polygon"}')) +POLYGON((0 0,1 0,1 1,0 1,0 0)) +# FeatureCollection: features before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[5,6]},"properties":{}}],"type":"FeatureCollection"}')); +ST_ASTEXT(ST_GEOMFROMGEOJSON('{"features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[5,6]},"properties":{}}],"type":"FeatureCollection"}')) +GEOMETRYCOLLECTION(POINT(5 6)) +# Feature: geometry before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometry":{"type":"Point","coordinates":[7,8]},"type":"Feature","properties":{}}')); +ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometry":{"type":"Point","coordinates":[7,8]},"type":"Feature","properties":{}}')) +POINT(7 8) +# Unknown keys interspersed (should be ignored) +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"foo":"bar","geometries":[],"type":"GeometryCollection","extra":123}')); +ST_ASTEXT(ST_GEOMFROMGEOJSON('{"foo":"bar","geometries":[],"type":"GeometryCollection","extra":123}')) +GEOMETRYCOLLECTION EMPTY +# Deeply nested geometries before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometries":[{"type":"GeometryCollection","geometries":[{"type":"Point","coordinates":[9,10]}]}],"type":"GeometryCollection"}')); +ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometries":[{"type":"GeometryCollection","geometries":[{"type":"Point","coordinates":[9,10]}]}],"type":"GeometryCollection"}')) +GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POINT(9 10))) +# End of test diff --git a/mysql-test/main/mdev_39981.test b/mysql-test/main/mdev_39981.test new file mode 100644 index 0000000000000..ed7c712327fe7 --- /dev/null +++ b/mysql-test/main/mdev_39981.test @@ -0,0 +1,38 @@ +--echo # +--echo # MDEV-39981: ST_GEOMFROMGEOJSON returns wrong result with reversed key order +--echo # + +--echo # GeometryCollection: type before geometries (baseline) +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"type":"GeometryCollection","geometries":[]}')); + +--echo # GeometryCollection: geometries before type (empty) +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometries":[],"type":"GeometryCollection"}')); + +--echo # GeometryCollection: geometries before type (non-empty) +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometries":[{"type":"Point","coordinates":[0,1]}],"type":"GeometryCollection"}')); + +--echo # GeometryCollection: multiple geometries before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometries":[{"type":"Point","coordinates":[1,2]},{"type":"Point","coordinates":[3,4]}],"type":"GeometryCollection"}')); + +--echo # Point: coordinates before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"coordinates":[0,1],"type":"Point"}')); + +--echo # LineString: coordinates before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"coordinates":[[0,0],[1,1],[2,2]],"type":"LineString"}')); + +--echo # Polygon: coordinates before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"coordinates":[[[0,0],[1,0],[1,1],[0,1],[0,0]]],"type":"Polygon"}')); + +--echo # FeatureCollection: features before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[5,6]},"properties":{}}],"type":"FeatureCollection"}')); + +--echo # Feature: geometry before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometry":{"type":"Point","coordinates":[7,8]},"type":"Feature","properties":{}}')); + +--echo # Unknown keys interspersed (should be ignored) +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"foo":"bar","geometries":[],"type":"GeometryCollection","extra":123}')); + +--echo # Deeply nested geometries before type +SELECT ST_ASTEXT(ST_GEOMFROMGEOJSON('{"geometries":[{"type":"GeometryCollection","geometries":[{"type":"Point","coordinates":[9,10]}]}],"type":"GeometryCollection"}')); + +--echo # End of test diff --git a/sql/spatial.cc b/sql/spatial.cc index 1d602b8578c76..c2fe73ecf3ddd 100644 --- a/sql/spatial.cc +++ b/sql/spatial.cc @@ -696,6 +696,8 @@ Geometry *Geometry::create_from_json(Geometry_buffer *buffer, coord_start= geom_start; goto create_geom; } + if (json_skip_level(je)) + goto err_return; } } else if (key_len == features_keyname_len && @@ -712,6 +714,8 @@ Geometry *Geometry::create_from_json(Geometry_buffer *buffer, features_start= je->value_begin; if (fcoll_type_found) goto handle_feature_collection; + if (json_skip_level(je)) + goto err_return; } } else if (key_len == geometry_keyname_len && @@ -724,6 +728,8 @@ Geometry *Geometry::create_from_json(Geometry_buffer *buffer, geometry_start= je->value_begin; if (feature_type_found) goto handle_geometry_key; + if (json_skip_level(je)) + goto err_return; } else goto err_return;