From 05fa3c0f574023d4e2beec240cefabd54e647f8c Mon Sep 17 00:00:00 2001 From: Akshat Nehra Date: Fri, 12 Jun 2026 02:29:03 +0000 Subject: [PATCH] MDEV-39998 Fix JSON_OVERLAPS asymmetry for nested array comparison json_compare_arrays_in_order() only checked if the second array (value) reached its end after the element-by-element comparison loop. This allowed a longer first array to falsely match a shorter second array as a prefix match, breaking the required symmetry of JSON_OVERLAPS. Fix: require both arrays to be at end state (JST_ARRAY_END or JST_OBJ_END) after the loop, ensuring arrays must have the same length to be considered equal in ordered comparison. 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_39998.result | 58 +++++++++++++++++++++++++++++++ mysql-test/main/mdev_39998.test | 51 +++++++++++++++++++++++++++ sql/item_jsonfunc.cc | 4 +-- 3 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 mysql-test/main/mdev_39998.result create mode 100644 mysql-test/main/mdev_39998.test diff --git a/mysql-test/main/mdev_39998.result b/mysql-test/main/mdev_39998.result new file mode 100644 index 0000000000000..f92a8ab6651bf --- /dev/null +++ b/mysql-test/main/mdev_39998.result @@ -0,0 +1,58 @@ +# +# MDEV-39998: JSON_OVERLAPS asymmetry for ordered array comparison +# +SELECT JSON_OVERLAPS('[[1,2]]', '[[1]]') AS must_be_0; +must_be_0 +0 +SELECT JSON_OVERLAPS('[[1]]', '[[1,2]]') AS must_be_0_too; +must_be_0_too +0 +SELECT JSON_OVERLAPS('[[1,2]]', '[[1,2]]') AS must_be_1; +must_be_1 +1 +SELECT JSON_OVERLAPS('[[1,2,3]]', '[[1,2]]') AS r1, +JSON_OVERLAPS('[[1,2]]', '[[1,2,3]]') AS r2; +r1 r2 +0 0 +SELECT JSON_OVERLAPS('[1,2,3]', '[3,4,5]') AS scalar_overlap; +scalar_overlap +1 +SELECT JSON_OVERLAPS('[[]]', '[[]]') AS empty_match; +empty_match +1 +SELECT JSON_OVERLAPS('[[1]]', '[[]]') AS r1, +JSON_OVERLAPS('[[]]', '[[1]]') AS r2; +r1 r2 +0 0 +SELECT JSON_OVERLAPS('[{"a":1}]', '[{"a":1}]') AS obj_match; +obj_match +1 +SELECT JSON_OVERLAPS('[{"a":1,"b":2}]', '[{"a":1}]') AS r1, +JSON_OVERLAPS('[{"a":1}]', '[{"a":1,"b":2}]') AS r2; +r1 r2 +0 0 +SELECT JSON_OVERLAPS('[[[1,2]]]', '[[[1]]]') AS r1, +JSON_OVERLAPS('[[[1]]]', '[[[1,2]]]') AS r2; +r1 r2 +0 0 +SELECT JSON_OVERLAPS('[[[1,2]]]', '[[[1,2]]]') AS deep_match; +deep_match +1 +SELECT JSON_OVERLAPS(NULL, '[[1]]') AS r1, +JSON_OVERLAPS('[[1]]', NULL) AS r2; +r1 r2 +NULL NULL +SELECT JSON_OVERLAPS('[1,[1,2]]', '[[1,2],1]') AS mixed_match; +mixed_match +1 +SELECT JSON_OVERLAPS('[1,[1,2]]', '[[1],1]') AS r1, +JSON_OVERLAPS('[[1],1]', '[1,[1,2]]') AS r2; +r1 r2 +1 1 +SELECT JSON_OVERLAPS('[[1]]', '[[1]]') AS single_match; +single_match +1 +SELECT JSON_OVERLAPS('[["a"]]', '[["a","b"]]') AS r1, +JSON_OVERLAPS('[["a","b"]]', '[["a"]]') AS r2; +r1 r2 +0 0 diff --git a/mysql-test/main/mdev_39998.test b/mysql-test/main/mdev_39998.test new file mode 100644 index 0000000000000..9dec2718189ea --- /dev/null +++ b/mysql-test/main/mdev_39998.test @@ -0,0 +1,51 @@ +# +# MDEV-39998: JSON_OVERLAPS must be symmetric for nested arrays +# + +--echo # +--echo # MDEV-39998: JSON_OVERLAPS asymmetry for ordered array comparison +--echo # + +# Original reported bug: prefix of nested array falsely matches +SELECT JSON_OVERLAPS('[[1,2]]', '[[1]]') AS must_be_0; +SELECT JSON_OVERLAPS('[[1]]', '[[1,2]]') AS must_be_0_too; + +# Exact match must still work +SELECT JSON_OVERLAPS('[[1,2]]', '[[1,2]]') AS must_be_1; + +# Both directions must agree (symmetry) +SELECT JSON_OVERLAPS('[[1,2,3]]', '[[1,2]]') AS r1, + JSON_OVERLAPS('[[1,2]]', '[[1,2,3]]') AS r2; + +# Scalar overlap still works +SELECT JSON_OVERLAPS('[1,2,3]', '[3,4,5]') AS scalar_overlap; + +# Empty nested arrays +SELECT JSON_OVERLAPS('[[]]', '[[]]') AS empty_match; +SELECT JSON_OVERLAPS('[[1]]', '[[]]') AS r1, + JSON_OVERLAPS('[[]]', '[[1]]') AS r2; + +# Nested objects inside arrays +SELECT JSON_OVERLAPS('[{"a":1}]', '[{"a":1}]') AS obj_match; +SELECT JSON_OVERLAPS('[{"a":1,"b":2}]', '[{"a":1}]') AS r1, + JSON_OVERLAPS('[{"a":1}]', '[{"a":1,"b":2}]') AS r2; + +# Deeply nested arrays +SELECT JSON_OVERLAPS('[[[1,2]]]', '[[[1]]]') AS r1, + JSON_OVERLAPS('[[[1]]]', '[[[1,2]]]') AS r2; +SELECT JSON_OVERLAPS('[[[1,2]]]', '[[[1,2]]]') AS deep_match; + +# NULL handling +SELECT JSON_OVERLAPS(NULL, '[[1]]') AS r1, + JSON_OVERLAPS('[[1]]', NULL) AS r2; + +# Mixed types in outer array +SELECT JSON_OVERLAPS('[1,[1,2]]', '[[1,2],1]') AS mixed_match; +SELECT JSON_OVERLAPS('[1,[1,2]]', '[[1],1]') AS r1, + JSON_OVERLAPS('[[1],1]', '[1,[1,2]]') AS r2; + +# Single element arrays +SELECT JSON_OVERLAPS('[[1]]', '[[1]]') AS single_match; +SELECT JSON_OVERLAPS('[["a"]]', '[["a","b"]]') AS r1, + JSON_OVERLAPS('[["a","b"]]', '[["a"]]') AS r2; + diff --git a/sql/item_jsonfunc.cc b/sql/item_jsonfunc.cc index 74477fd506e0d..11859fe8d92af 100644 --- a/sql/item_jsonfunc.cc +++ b/sql/item_jsonfunc.cc @@ -5071,8 +5071,8 @@ bool json_compare_arrays_in_order(json_engine_t *js, json_engine_t *value, return FALSE; } } - res= (value->state == JST_ARRAY_END || value->state == JST_OBJ_END ? - TRUE : FALSE); + res= (value->state == JST_ARRAY_END || value->state == JST_OBJ_END) && + (js->state == JST_ARRAY_END || js->state == JST_OBJ_END); json_skip_current_level(js, value); return res; }