Skip to content

Commit 91d35c3

Browse files
committed
MDEV-38591: Address latest grooverdan review feedback
- Section 11: use LONGTEXT instead of JSON column to avoid dependency on check_constraint_checks which will be disabled in PR MariaDB#5263 - Remove remaining SHOW WARNINGS from section 13.7 (MTR outputs warnings automatically) - Remove '(mentor)' annotations from echo labels in sections 15, 19, 20 - NULL candidate semantics: remove args[0]->null_value check, allowing NULL MEMBER OF ('[null]') = 1 (SQL NULL treated as JSON null via json_quote_item returning 'null' per MDEV-13645). Intentional divergence from MySQL documented behavior per grooverdan's request. - Simplify stored function tests: single-line CREATE FUNCTION without delimiters, positive VARCHAR test (f() RETURNS VARCHAR), JSON-returning function test (j_row_result() RETURNS JSON) - Add optimizer trace test (section 22) showing MEMBER OF participates correctly in equality_propagation and condition_processing - Add set_maybe_null() in fix_length_and_dec() for cursor protocol compatibility per grooverdan
1 parent ef9a977 commit 91d35c3

3 files changed

Lines changed: 105 additions & 87 deletions

File tree

mysql-test/suite/json/r/member_of.result

Lines changed: 69 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ SELECT 2 MEMBER OF ('["1","2","3"]');
2222
# 4. SQL NULL propagation checks
2323
SELECT NULL MEMBER OF ('[1,2,3]');
2424
NULL MEMBER OF ('[1,2,3]')
25-
NULL
25+
0
2626
SELECT NULL MEMBER OF ('[1,2,3]') IS NULL;
2727
NULL MEMBER OF ('[1,2,3]') IS NULL
28-
1
28+
0
2929
SELECT 1 MEMBER OF (NULL);
3030
1 MEMBER OF (NULL)
3131
NULL
@@ -71,17 +71,17 @@ id val 2 MEMBER OF (val)
7171
3 NULL NULL
7272
SELECT id, val, NULL MEMBER OF (val) FROM t1;
7373
id val NULL MEMBER OF (val)
74-
1 [1, 2, 3] NULL
75-
2 [4, 5, 6] NULL
74+
1 [1, 2, 3] 0
75+
2 [4, 5, 6] 0
7676
3 NULL NULL
7777
CREATE TABLE t2 (candidate INT);
7878
INSERT INTO t2 VALUES (2), (4), (NULL);
7979
SELECT candidate, val, candidate MEMBER OF (val) FROM t1, t2 ORDER BY id, candidate;
8080
candidate val candidate MEMBER OF (val)
81-
NULL [1, 2, 3] NULL
81+
NULL [1, 2, 3] 0
8282
2 [1, 2, 3] 1
8383
4 [1, 2, 3] 0
84-
NULL [4, 5, 6] NULL
84+
NULL [4, 5, 6] 0
8585
2 [4, 5, 6] 0
8686
4 [4, 5, 6] 1
8787
NULL NULL NULL
@@ -112,14 +112,12 @@ EXECUTE stmt2 USING @candidate_json, @container_nested;
112112
JSON_COMPACT(?) MEMBER OF (?)
113113
1
114114
DEALLOCATE PREPARE stmt2;
115-
# 11. Malformed JSON-typed candidate test (using CHECK constraints bypass)
116-
CREATE TABLE t3 (val JSON);
117-
SET STATEMENT check_constraint_checks=0 FOR INSERT INTO t3 VALUES ('[1,2');
115+
# 11. Malformed LONGTEXT candidate test
116+
CREATE TABLE t3 (val LONGTEXT);
117+
INSERT INTO t3 VALUES ('[1,2');
118118
SELECT val MEMBER OF ('[1,2,3]') FROM t3;
119119
val MEMBER OF ('[1,2,3]')
120-
NULL
121-
Warnings:
122-
Warning 4037 Unexpected end of JSON text in argument 2 to function 'json_contains'
120+
0
123121
DROP TABLE t3;
124122
# 12. JSON scalar candidate test (type-strictness in passthrough branch)
125123
CREATE TABLE t4 (val JSON);
@@ -142,13 +140,13 @@ SELECT 4 NOT MEMBER OF ('[1,2,3]');
142140
SELECT '2' NOT MEMBER OF ('[1,2,3]');
143141
'2' NOT MEMBER OF ('[1,2,3]')
144142
1
145-
# 13.4 NULL NOT MEMBER OF ('[1,2,3]') -> NULL (and IS NULL -> 1)
143+
# 13.4 NULL NOT MEMBER OF ('[1,2,3]') -> 1
146144
SELECT NULL NOT MEMBER OF ('[1,2,3]');
147145
NULL NOT MEMBER OF ('[1,2,3]')
148-
NULL
146+
1
149147
SELECT NULL NOT MEMBER OF ('[1,2,3]') IS NULL;
150148
NULL NOT MEMBER OF ('[1,2,3]') IS NULL
151-
1
149+
0
152150
# 13.5 1 NOT MEMBER OF (NULL) -> NULL (and IS NULL -> 1)
153151
SELECT 1 NOT MEMBER OF (NULL);
154152
1 NOT MEMBER OF (NULL)
@@ -168,9 +166,6 @@ id select_type table type possible_keys key key_len ref rows filtered Extra
168166
1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used
169167
Warnings:
170168
Note 1003 select 1 not member of ('[1,2,3]') AS `1 NOT MEMBER OF ('[1,2,3]')`
171-
SHOW WARNINGS;
172-
Level Code Message
173-
Note 1003 select 1 not member of ('[1,2,3]') AS `1 NOT MEMBER OF ('[1,2,3]')`
174169
# 14. Prepared Statements twice execution for Section 6 nested cases
175170
PREPARE stmt3 FROM 'SELECT JSON_COMPACT(\'[1,2]\') MEMBER OF (\'[[1,2],[3,4]]\')';
176171
EXECUTE stmt3;
@@ -189,24 +184,24 @@ JSON_COMPACT('{"name":"John"}') MEMBER OF ('[{"name":"John"},{"name":"Joe"}]')
189184
1
190185
DEALLOCATE PREPARE stmt4;
191186
#
192-
# 15. Item 12 (mentor): null literal vs JSON null \u2014 type-strict distinctions
187+
# 15. null literal vs JSON null type-strict distinctions
193188
#
194189
# SQL string 'null' inside a JSON string-array ["null"] -> 1 (matches the string)
195190
select 'null' member of ('["null"]');
196191
'null' member of ('["null"]')
197192
1
198-
# SQL NULL inside an array containing JSON null literal [null] -> NULL (SQL NULL propagates)
193+
# SQL NULL inside an array containing JSON null literal [null] -> 1
199194
select null member of ('[null]');
200195
null member of ('[null]')
201-
NULL
196+
1
202197
# SQL string 'null' inside an array containing JSON null literal [null] -> 0 (type mismatch)
203198
select 'null' member of ('[null]');
204199
'null' member of ('[null]')
205200
0
206-
# SQL NULL inside a JSON string-array ["null"] -> NULL (SQL NULL propagates)
201+
# SQL NULL inside a JSON string-array ["null"] -> 0 (type mismatch)
207202
select null member of ('["null"]');
208203
null member of ('["null"]')
209-
NULL
204+
0
210205
#
211206
# 16. Regression test for Item 1 root-cause fix (stale a2_parsed cache bug)
212207
# Before the fix, cross-join with non-constant candidate produced wrong results:
@@ -220,15 +215,15 @@ INSERT INTO t5 VALUES (1, '[1, 2, 3]'), (2, '[4, 5, 6]'), (3, NULL);
220215
CREATE TABLE t6 (candidate INT);
221216
INSERT INTO t6 VALUES (2), (4), (NULL);
222217
# Exact results expected after fix:
223-
# (NULL, [1,2,3]) -> NULL, (2, [1,2,3]) -> 1, (4, [1,2,3]) -> 0
224-
# (NULL, [4,5,6]) -> NULL, (2, [4,5,6]) -> 0, (4, [4,5,6]) -> 1
218+
# (NULL, [1,2,3]) -> 0, (2, [1,2,3]) -> 1, (4, [1,2,3]) -> 0
219+
# (NULL, [4,5,6]) -> 0, (2, [4,5,6]) -> 0, (4, [4,5,6]) -> 1
225220
# (NULL, NULL) -> NULL, (2, NULL) -> NULL, (4, NULL) -> NULL
226221
SELECT candidate, val, candidate MEMBER OF (val) FROM t5, t6 ORDER BY id, candidate;
227222
candidate val candidate MEMBER OF (val)
228-
NULL [1, 2, 3] NULL
223+
NULL [1, 2, 3] 0
229224
2 [1, 2, 3] 1
230225
4 [1, 2, 3] 0
231-
NULL [4, 5, 6] NULL
226+
NULL [4, 5, 6] 0
232227
2 [4, 5, 6] 0
233228
4 [4, 5, 6] 1
234229
NULL NULL NULL
@@ -271,22 +266,23 @@ a j k t7.a MEMBER OF (t8.k)
271266
1 [1,2,3] [1,2,3] 1
272267
DROP TABLE t7, t8;
273268
#
274-
# 19. Item 6 (mentor): Single-column ROW_RESULT via stored function call
275-
# Stored function calls (Item_func_sp) report ROW_RESULT with cols() == 1
276-
# at the point val_str() inspects args[0]->result_type() — the goto-eval unwrap
277-
# path is reached through ordinary stored function calls.
269+
# 19. Stored function tests (VARCHAR and JSON return types)
278270
#
279-
CREATE FUNCTION f_row_result() RETURNS VARCHAR(50)
280-
RETURN JSON_OBJECT('value', 2, 'key', 'golden')//
281-
SELECT f_row_result() MEMBER OF ('[{"key": "golden", "value": 2E0}]');
282-
f_row_result() MEMBER OF ('[{"key": "golden", "value": 2E0}]')
271+
CREATE FUNCTION f() RETURNS VARCHAR(30) RETURN 'pony';
272+
SELECT f() MEMBER OF ('["sheep", "lamb", "pony"]');
273+
f() MEMBER OF ('["sheep", "lamb", "pony"]')
274+
1
275+
SELECT JSON_QUOTE(f());
276+
JSON_QUOTE(f())
277+
"pony"
278+
DROP FUNCTION f;
279+
CREATE FUNCTION j_row_result() RETURNS JSON RETURN JSON_OBJECT('value', 2, 'key', 'golden');
280+
SELECT j_row_result() MEMBER OF ('[{"key": "golden", "value": 2E0}]');
281+
j_row_result() MEMBER OF ('[{"key": "golden", "value": 2E0}]')
283282
0
284-
SELECT JSON_QUOTE(f_row_result());
285-
JSON_QUOTE(f_row_result())
286-
"{\"value\": 2, \"key\": \"golden\"}"
287-
DROP FUNCTION f_row_result;
283+
DROP FUNCTION j_row_result;
288284
#
289-
# 20. Item 4 (mentor): TIME_RESULT tests
285+
# 20. TIME_RESULT tests
290286
# Verify that TIME values are quoted as JSON string literals without escaping.
291287
#
292288
SELECT JSON_QUOTE(CAST('12:30:00' AS TIME));
@@ -303,12 +299,41 @@ CAST('12:30:00' AS TIME) MEMBER OF ('["10:00:00", "12:30:00"]')
303299
# Confirms literal multi-column ROW() constructors and RETURNS ROW(...) functions
304300
# are rejected by check_cols(1) during fix_fields() at bind time.
305301
#
306-
CREATE FUNCTION f1_row() RETURNS ROW(a INT)
307-
BEGIN
308-
RETURN (SELECT 42);
309-
END//
302+
CREATE FUNCTION f1_row() RETURNS ROW(a INT) RETURN (SELECT 42);
310303
SELECT JSON_QUOTE(f1_row());
311304
ERROR 21000: Operand should contain 1 column(s)
312305
SELECT f1_row() MEMBER OF ('[42]');
313306
ERROR 21000: Operand should contain 1 column(s)
314307
DROP FUNCTION f1_row;
308+
#
309+
# 22. Optimizer trace test for condition processing
310+
#
311+
CREATE TABLE t (id INT PRIMARY KEY, x INT, j JSON);
312+
INSERT INTO t VALUES (1, 10, '[10, 20]'), (2, 20, '[10, 20]'), (3, 30, '[30, 40]');
313+
SET STATEMENT optimizer_trace='enabled=on' FOR SELECT * FROM t WHERE x=10 AND x MEMBER OF (j);
314+
id x j
315+
1 10 [10, 20]
316+
SELECT JSON_DETAILED(JSON_EXTRACT(TRACE, '$**.condition_processing')) AS t FROM information_schema.OPTIMIZER_TRACE;
317+
t
318+
[
319+
{
320+
"condition": "WHERE",
321+
"original_condition": "t.x = 10 and t.x member of (t.j)",
322+
"steps":
323+
[
324+
{
325+
"transformation": "equality_propagation",
326+
"resulting_condition": "10 member of (t.j) and multiple equal(10, t.x)"
327+
},
328+
{
329+
"transformation": "constant_propagation",
330+
"resulting_condition": "10 member of (t.j) and multiple equal(10, t.x)"
331+
},
332+
{
333+
"transformation": "trivial_condition_removal",
334+
"resulting_condition": "10 member of (t.j) and multiple equal(10, t.x)"
335+
}
336+
]
337+
}
338+
]
339+
DROP TABLE t;

mysql-test/suite/json/t/member_of.test

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ EXECUTE stmt2 USING @candidate_json, @container_nested;
6565
EXECUTE stmt2 USING @candidate_json, @container_nested;
6666
DEALLOCATE PREPARE stmt2;
6767

68-
--echo # 11. Malformed JSON-typed candidate test (using CHECK constraints bypass)
69-
CREATE TABLE t3 (val JSON);
70-
SET STATEMENT check_constraint_checks=0 FOR INSERT INTO t3 VALUES ('[1,2');
68+
--echo # 11. Malformed LONGTEXT candidate test
69+
CREATE TABLE t3 (val LONGTEXT);
70+
INSERT INTO t3 VALUES ('[1,2');
7171
SELECT val MEMBER OF ('[1,2,3]') FROM t3;
7272
DROP TABLE t3;
7373

@@ -87,7 +87,7 @@ SELECT 4 NOT MEMBER OF ('[1,2,3]');
8787
--echo # 13.3 '2' NOT MEMBER OF ('[1,2,3]') -> 1 (type-strict comparison)
8888
SELECT '2' NOT MEMBER OF ('[1,2,3]');
8989

90-
--echo # 13.4 NULL NOT MEMBER OF ('[1,2,3]') -> NULL (and IS NULL -> 1)
90+
--echo # 13.4 NULL NOT MEMBER OF ('[1,2,3]') -> 1
9191
SELECT NULL NOT MEMBER OF ('[1,2,3]');
9292
SELECT NULL NOT MEMBER OF ('[1,2,3]') IS NULL;
9393

@@ -100,7 +100,6 @@ SELECT 1 NOT MEMBER OF ('not_json');
100100

101101
--echo # 13.7 EXPLAIN EXTENDED round-trip print check
102102
EXPLAIN EXTENDED SELECT 1 NOT MEMBER OF ('[1,2,3]');
103-
SHOW WARNINGS;
104103

105104

106105
--echo # 14. Prepared Statements twice execution for Section 6 nested cases
@@ -115,15 +114,15 @@ EXECUTE stmt4;
115114
DEALLOCATE PREPARE stmt4;
116115

117116
--echo #
118-
--echo # 15. Item 12 (mentor): null literal vs JSON null \u2014 type-strict distinctions
117+
--echo # 15. null literal vs JSON null type-strict distinctions
119118
--echo #
120119
--echo # SQL string 'null' inside a JSON string-array ["null"] -> 1 (matches the string)
121120
select 'null' member of ('["null"]');
122-
--echo # SQL NULL inside an array containing JSON null literal [null] -> NULL (SQL NULL propagates)
121+
--echo # SQL NULL inside an array containing JSON null literal [null] -> 1
123122
select null member of ('[null]');
124123
--echo # SQL string 'null' inside an array containing JSON null literal [null] -> 0 (type mismatch)
125124
select 'null' member of ('[null]');
126-
--echo # SQL NULL inside a JSON string-array ["null"] -> NULL (SQL NULL propagates)
125+
--echo # SQL NULL inside a JSON string-array ["null"] -> 0 (type mismatch)
127126
select null member of ('["null"]');
128127

129128
--echo #
@@ -140,8 +139,8 @@ CREATE TABLE t6 (candidate INT);
140139
INSERT INTO t6 VALUES (2), (4), (NULL);
141140

142141
--echo # Exact results expected after fix:
143-
--echo # (NULL, [1,2,3]) -> NULL, (2, [1,2,3]) -> 1, (4, [1,2,3]) -> 0
144-
--echo # (NULL, [4,5,6]) -> NULL, (2, [4,5,6]) -> 0, (4, [4,5,6]) -> 1
142+
--echo # (NULL, [1,2,3]) -> 0, (2, [1,2,3]) -> 1, (4, [1,2,3]) -> 0
143+
--echo # (NULL, [4,5,6]) -> 0, (2, [4,5,6]) -> 0, (4, [4,5,6]) -> 1
145144
--echo # (NULL, NULL) -> NULL, (2, NULL) -> NULL, (4, NULL) -> NULL
146145
SELECT candidate, val, candidate MEMBER OF (val) FROM t5, t6 ORDER BY id, candidate;
147146

@@ -181,23 +180,19 @@ SELECT t7.a, t7.j, t8.k, t7.a MEMBER OF (t8.k) FROM t7, t8
181180
DROP TABLE t7, t8;
182181

183182
--echo #
184-
--echo # 19. Item 6 (mentor): Single-column ROW_RESULT via stored function call
185-
--echo # Stored function calls (Item_func_sp) report ROW_RESULT with cols() == 1
186-
--echo # at the point val_str() inspects args[0]->result_type() — the goto-eval unwrap
187-
--echo # path is reached through ordinary stored function calls.
183+
--echo # 19. Stored function tests (VARCHAR and JSON return types)
188184
--echo #
189-
--delimiter //
190-
CREATE FUNCTION f_row_result() RETURNS VARCHAR(50)
191-
RETURN JSON_OBJECT('value', 2, 'key', 'golden')//
192-
--delimiter ;
185+
CREATE FUNCTION f() RETURNS VARCHAR(30) RETURN 'pony';
186+
SELECT f() MEMBER OF ('["sheep", "lamb", "pony"]');
187+
SELECT JSON_QUOTE(f());
188+
DROP FUNCTION f;
193189

194-
SELECT f_row_result() MEMBER OF ('[{"key": "golden", "value": 2E0}]');
195-
SELECT JSON_QUOTE(f_row_result());
196-
197-
DROP FUNCTION f_row_result;
190+
CREATE FUNCTION j_row_result() RETURNS JSON RETURN JSON_OBJECT('value', 2, 'key', 'golden');
191+
SELECT j_row_result() MEMBER OF ('[{"key": "golden", "value": 2E0}]');
192+
DROP FUNCTION j_row_result;
198193

199194
--echo #
200-
--echo # 20. Item 4 (mentor): TIME_RESULT tests
195+
--echo # 20. TIME_RESULT tests
201196
--echo # Verify that TIME values are quoted as JSON string literals without escaping.
202197
--echo #
203198
SELECT JSON_QUOTE(CAST('12:30:00' AS TIME));
@@ -209,12 +204,7 @@ SELECT CAST('12:30:00' AS TIME) MEMBER OF ('["10:00:00", "12:30:00"]');
209204
--echo # Confirms literal multi-column ROW() constructors and RETURNS ROW(...) functions
210205
--echo # are rejected by check_cols(1) during fix_fields() at bind time.
211206
--echo #
212-
--delimiter //
213-
CREATE FUNCTION f1_row() RETURNS ROW(a INT)
214-
BEGIN
215-
RETURN (SELECT 42);
216-
END//
217-
--delimiter ;
207+
CREATE FUNCTION f1_row() RETURNS ROW(a INT) RETURN (SELECT 42);
218208

219209
--error ER_OPERAND_COLUMNS
220210
SELECT JSON_QUOTE(f1_row());
@@ -223,3 +213,11 @@ SELECT f1_row() MEMBER OF ('[42]');
223213

224214
DROP FUNCTION f1_row;
225215

216+
--echo #
217+
--echo # 22. Optimizer trace test for condition processing
218+
--echo #
219+
CREATE TABLE t (id INT PRIMARY KEY, x INT, j JSON);
220+
INSERT INTO t VALUES (1, 10, '[10, 20]'), (2, 20, '[10, 20]'), (3, 30, '[30, 40]');
221+
SET STATEMENT optimizer_trace='enabled=on' FOR SELECT * FROM t WHERE x=10 AND x MEMBER OF (j);
222+
SELECT JSON_DETAILED(JSON_EXTRACT(TRACE, '$**.condition_processing')) AS t FROM information_schema.OPTIMIZER_TRACE;
223+
DROP TABLE t;

sql/item_jsonfunc.cc

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6703,23 +6703,16 @@ bool Item_func_member_of::val_bool()
67036703
{
67046704
DBUG_ASSERT(fixed());
67056705

6706-
/*
6707-
Item 11: Evaluated json_contains_item directly. The previous check
6708-
if (is_json_type(args[0])) { args[0]->val_json(&tmp_candidate); ... }
6709-
was redundant because json_contains_item->val_bool() evaluates args[0]
6710-
(passed directly as args[1] of json_contains_item when is_json_type is true)
6711-
and sets json_contains_item->null_value appropriately.
6712-
*/
67136706
bool res= json_contains_item->val_bool();
67146707
/*
6715-
Item 8: json_contains_item->null_value must be checked first. It is only
6716-
after Item_json_contains::val_bool() runs that args[0] (wrapped inside
6717-
json_contains_item as args[1] of that call, possibly via
6718-
Item_func_json_quote) has actually been evaluated via val_json().
6719-
Checking args[0]->null_value before json_contains_item->val_bool() has
6720-
been called would read a stale/unset null_value from the previous row.
6708+
Note: MariaDB intentionally diverges from MySQL's documented behavior
6709+
("If value is NULL, returns NULL"). MariaDB evaluates SELECT NULL MEMBER OF
6710+
('[null]') as 1 and SELECT NULL MEMBER OF ('[1,2,3]') as 0. When args[0] is
6711+
SQL NULL, json_quote_item returns the string "null" (per MDEV-13645), which
6712+
json_contains_item matches against JSON null in the array. Container NULL
6713+
(args[1] IS NULL) still propagates SQL NULL via json_contains_item->null_value.
67216714
*/
6722-
if (json_contains_item->null_value || args[0]->null_value)
6715+
if (json_contains_item->null_value)
67236716
{
67246717
null_value= 1;
67256718
return false;
@@ -6740,6 +6733,8 @@ bool Item_func_member_of::fix_length_and_dec(THD *thd)
67406733
if (Item_func_opt_neg::fix_length_and_dec(thd))
67416734
return true;
67426735

6736+
set_maybe_null();
6737+
67436738
List<Item> contains_args;
67446739
if (contains_args.push_back(args[1], thd->mem_root))
67456740
return true;

0 commit comments

Comments
 (0)