From 03bda00fcc69d56f1c069ad1240c901c552679d1 Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Wed, 9 Sep 2026 15:45:07 -0400 Subject: [PATCH] MDEV-36166: Accept bracketed points inside MULTIPOINT ST_GEOMFROMTEXT('MULTIPOINT((0 0),(1 1))') returned NULL while ST_GEOMFROMTEXT('MULTIPOINT(0 0,1 1)') returned the geometry. The bracketed spelling is the one the OGC WKT grammar defines. In 06-103r4 section 7.2.2 a is a list of , and a has its own parentheses, the same way a is a list of . The bare spelling matches no production in that grammar, so the text MariaDB rejected was the conformant one. The first point now determines which of the two bracketing forms the remaining list elements will use. A mixed list such as MULTIPOINT((0 0),1 1) is an error. The bare form stays accepted because existing data and applications use it. Geometry::create_from_wkt is the single entry into the WKT reader, so ST_MPOINTFROMTEXT and a MULTIPOINT nested in a GEOMETRYCOLLECTION are covered by the same change. Co-Authored-By: Claude Opus 5 --- mysql-test/main/gis.result | 58 ++++++++++++++++++++++++++++++++++++++ mysql-test/main/gis.test | 37 ++++++++++++++++++++++++ sql/spatial.cc | 17 ++++++++++- 3 files changed, 111 insertions(+), 1 deletion(-) diff --git a/mysql-test/main/gis.result b/mysql-test/main/gis.result index d60f8da2d5d51..40c81dd9a5913 100644 --- a/mysql-test/main/gis.result +++ b/mysql-test/main/gis.result @@ -5654,3 +5654,61 @@ SELECT ST_GEOMFROMWKB (0x0104000000010000008201000000000000000000000000000000000 multipoint_bad_inner_bo NULL # End of 10.11 tests +# +# MDEV-36166 support for notation with brackets inside MULTIPOINT +# +SELECT ST_ASTEXT(ST_GEOMFROMTEXT('MULTIPOINT((0 0))')) AS one_bracketed; +one_bracketed +MULTIPOINT(0 0) +SELECT ST_ASTEXT(ST_GEOMFROMTEXT('MULTIPOINT((0 0),(1 1))')) AS bracketed; +bracketed +MULTIPOINT(0 0,1 1) +SELECT ST_ASTEXT(ST_GEOMFROMTEXT('MULTIPOINT(0 0,1 1)')) AS bare; +bare +MULTIPOINT(0 0,1 1) +SELECT ST_ASTEXT(ST_GEOMFROMTEXT('MULTIPOINT ( ( 0 0 ) , ( 1 1 ) )')) AS spaced; +spaced +MULTIPOINT(0 0,1 1) +SELECT ST_NUMGEOMETRIES(ST_GEOMFROMTEXT('MULTIPOINT((0 0),(1 1),(2 2))')) AS n; +n +3 +# The two spellings may not be mixed within one MULTIPOINT. +SELECT ST_ASTEXT(ST_GEOMFROMTEXT('MULTIPOINT((0 0),1 1)')) AS mixed_open; +mixed_open +NULL +SELECT ST_ASTEXT(ST_GEOMFROMTEXT('MULTIPOINT(0 0,(1 1))')) AS mixed_close; +mixed_close +NULL +# An opened bracket must be closed after the point it opened. +SELECT ST_ASTEXT(ST_GEOMFROMTEXT('MULTIPOINT((0 0,1 1))')) AS unclosed; +unclosed +NULL +SELECT ST_ASTEXT(ST_GEOMFROMTEXT('MULTIPOINT((0 0),(1 1)')) AS truncated; +truncated +NULL +# The text may end where the first point would decide the spelling. +SELECT ST_ASTEXT(ST_GEOMFROMTEXT('MULTIPOINT(')) AS ends_at_decision; +ends_at_decision +NULL +SELECT ST_ASTEXT(ST_GEOMFROMTEXT('MULTIPOINT()')) AS empty_list; +empty_list +NULL +# Brackets around a point are a MULTIPOINT spelling only. +SELECT ST_ASTEXT(ST_GEOMFROMTEXT('LINESTRING((0 0),(1 1))')) AS linestring; +linestring +NULL +# Both spellings reach the same reader through the other WKT entries. +SELECT ST_ASTEXT(ST_MPOINTFROMTEXT('MULTIPOINT((0 0),(1 1))')) AS mpointfromtext; +mpointfromtext +MULTIPOINT(0 0,1 1) +SELECT ST_ASTEXT(ST_GEOMFROMTEXT( +'GEOMETRYCOLLECTION(MULTIPOINT((1 1),(2 2)))')) AS in_collection; +in_collection +GEOMETRYCOLLECTION(MULTIPOINT(1 1,2 2)) +CREATE TABLE t1 (g GEOMETRY); +INSERT INTO t1 VALUES(ST_GEOMFROMTEXT('MULTIPOINT((1 1),(2 2))')); +SELECT ST_ASTEXT(g) FROM t1; +ST_ASTEXT(g) +MULTIPOINT(1 1,2 2) +DROP TABLE t1; +# End of 11.4 tests diff --git a/mysql-test/main/gis.test b/mysql-test/main/gis.test index 1efed8d076903..481740a5acf2a 100644 --- a/mysql-test/main/gis.test +++ b/mysql-test/main/gis.test @@ -3613,3 +3613,40 @@ SELECT ST_GEOMFROMWKB (0x0104000000010000000201000000000000000000000000000000000 SELECT ST_GEOMFROMWKB (0x0104000000010000008201000000000000000000000000000000000000000000000000) as multipoint_bad_inner_bo; --echo # End of 10.11 tests + +--echo # +--echo # MDEV-36166 support for notation with brackets inside MULTIPOINT +--echo # + +SELECT ST_ASTEXT(ST_GEOMFROMTEXT('MULTIPOINT((0 0))')) AS one_bracketed; +SELECT ST_ASTEXT(ST_GEOMFROMTEXT('MULTIPOINT((0 0),(1 1))')) AS bracketed; +SELECT ST_ASTEXT(ST_GEOMFROMTEXT('MULTIPOINT(0 0,1 1)')) AS bare; +SELECT ST_ASTEXT(ST_GEOMFROMTEXT('MULTIPOINT ( ( 0 0 ) , ( 1 1 ) )')) AS spaced; +SELECT ST_NUMGEOMETRIES(ST_GEOMFROMTEXT('MULTIPOINT((0 0),(1 1),(2 2))')) AS n; + +--echo # The two spellings may not be mixed within one MULTIPOINT. +SELECT ST_ASTEXT(ST_GEOMFROMTEXT('MULTIPOINT((0 0),1 1)')) AS mixed_open; +SELECT ST_ASTEXT(ST_GEOMFROMTEXT('MULTIPOINT(0 0,(1 1))')) AS mixed_close; + +--echo # An opened bracket must be closed after the point it opened. +SELECT ST_ASTEXT(ST_GEOMFROMTEXT('MULTIPOINT((0 0,1 1))')) AS unclosed; +SELECT ST_ASTEXT(ST_GEOMFROMTEXT('MULTIPOINT((0 0),(1 1)')) AS truncated; + +--echo # The text may end where the first point would decide the spelling. +SELECT ST_ASTEXT(ST_GEOMFROMTEXT('MULTIPOINT(')) AS ends_at_decision; +SELECT ST_ASTEXT(ST_GEOMFROMTEXT('MULTIPOINT()')) AS empty_list; + +--echo # Brackets around a point are a MULTIPOINT spelling only. +SELECT ST_ASTEXT(ST_GEOMFROMTEXT('LINESTRING((0 0),(1 1))')) AS linestring; + +--echo # Both spellings reach the same reader through the other WKT entries. +SELECT ST_ASTEXT(ST_MPOINTFROMTEXT('MULTIPOINT((0 0),(1 1))')) AS mpointfromtext; +SELECT ST_ASTEXT(ST_GEOMFROMTEXT( + 'GEOMETRYCOLLECTION(MULTIPOINT((1 1),(2 2)))')) AS in_collection; + +CREATE TABLE t1 (g GEOMETRY); +INSERT INTO t1 VALUES(ST_GEOMFROMTEXT('MULTIPOINT((1 1),(2 2))')); +SELECT ST_ASTEXT(g) FROM t1; +DROP TABLE t1; + +--echo # End of 11.4 tests diff --git a/sql/spatial.cc b/sql/spatial.cc index 1484ae3d32297..e35ed645ef5fc 100644 --- a/sql/spatial.cc +++ b/sql/spatial.cc @@ -2164,18 +2164,33 @@ bool Gis_multi_point::init_from_wkt(Gis_read_stream *trs, String *wkb) uint32 n_points= 0; uint32 np_pos= wkb->length(); Gis_point p; + char next_sym; if (wkb->reserve(4, 512)) return 1; wkb->length(wkb->length()+4); // Reserve space for points + /* + MULTIPOINT(0 0,1 1) and MULTIPOINT((0 0),(1 1)) name the same geometry. + The first point fixes which of the two spellings the whole list uses, + and every later point repeats it. A mixed list such as + MULTIPOINT((0 0),1 1) is rejected. No check tests for mixing itself. + The point that breaks the pattern fails to parse under the spelling + already fixed. + */ + if (!(next_sym= trs->next_symbol())) + return 1; + const bool bracketed= next_sym == '('; + for (;;) { if (wkb->reserve(1 + 4, 512)) return 1; wkb->q_append((char) wkb_ndr); wkb->q_append((uint32) wkb_point); - if (p.init_from_wkt(trs, wkb)) + if ((bracketed && trs->check_next_symbol('(')) || + p.init_from_wkt(trs, wkb) || + (bracketed && trs->check_next_symbol(')'))) return 1; n_points++; if (trs->skip_char(',')) // Didn't find ','