Skip to content

Commit 5f7e883

Browse files
committed
MDEV-36322 Comparison ROW(stored_func(),1)=ROW(1,1) calls the function twice per row
Item_func_sp::execute() was called two times per row in this scenario: SELECT ROW(f1(),1) = ROW(1,1), @counter FROM seq_1_to_5; - the first time from Item_func_sp::bring_value() - the second time from Item_func_sp::val_int() Fix: Changing Item_func_sp::bring_value() to call execute() only when the result type is ROW_RESULT.
1 parent c34bb80 commit 5f7e883

File tree

4 files changed

+312
-2
lines changed

4 files changed

+312
-2
lines changed

mysql-test/main/sp-row.result

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2383,3 +2383,130 @@ DROP PROCEDURE p1;
23832383
#
23842384
# End of 11.7 tests
23852385
#
2386+
#
2387+
# MDEV-36322 Comparison ROW(stored_func(),1)=ROW(1,1) calls the function twice per row
2388+
#
2389+
CREATE FUNCTION f1() RETURNS INT
2390+
BEGIN
2391+
SET @counter= COALESCE(@counter, 0) + 1;
2392+
RETURN @counter;
2393+
END;
2394+
/
2395+
#
2396+
# Queries without ROW comparison
2397+
#
2398+
SET @counter=0;
2399+
SELECT f1() FROM seq_1_to_5;
2400+
f1()
2401+
1
2402+
2
2403+
3
2404+
4
2405+
5
2406+
SET @counter=0;
2407+
SELECT f1() AS f FROM seq_1_to_5 ORDER BY f;
2408+
f
2409+
1
2410+
2
2411+
3
2412+
4
2413+
5
2414+
SET @counter=0;
2415+
SELECT f1() AS f FROM seq_1_to_5 ORDER BY f DESC;
2416+
f
2417+
5
2418+
4
2419+
3
2420+
2
2421+
1
2422+
SET @counter=0;
2423+
SELECT f1()=1 AS eq, @counter AS counter FROM seq_1_to_5;
2424+
eq counter
2425+
1 1
2426+
0 2
2427+
0 3
2428+
0 4
2429+
0 5
2430+
#
2431+
# Queries without ROW comparison + HAVING
2432+
# The counter is incremented by 2 per row.
2433+
#
2434+
SET @counter=0;
2435+
SELECT f1() AS f FROM seq_1_to_5 HAVING f1()<>0;
2436+
f
2437+
2
2438+
4
2439+
6
2440+
8
2441+
10
2442+
SET @counter=0;
2443+
SELECT f1() AS f FROM seq_1_to_5 HAVING f<>0;
2444+
f
2445+
2
2446+
4
2447+
6
2448+
8
2449+
10
2450+
#
2451+
# Queries with ROW comparison.
2452+
# Item_row::bring_value() is called on the left side, which calls
2453+
# Item_func_sp::bring_value() for f1(),
2454+
# which does *not* call Item_func_sp::execute()
2455+
# because the return type of f1() is scalar.
2456+
# Item_func_sp::execute() will be called from Item_func_sp::val_int()
2457+
# from Arg_comparator::compare_int_signed().
2458+
#
2459+
SET @counter=0;
2460+
SELECT ROW(f1(),1) = ROW(1,1) AS eq, @counter AS counter FROM seq_1_to_5;
2461+
eq counter
2462+
1 1
2463+
0 2
2464+
0 3
2465+
0 4
2466+
0 5
2467+
SET @counter=0;
2468+
SELECT ROW(COALESCE(f1()),1) = ROW(1,1) AS eq, @counter AS counter FROM seq_1_to_5;
2469+
eq counter
2470+
1 1
2471+
0 2
2472+
0 3
2473+
0 4
2474+
0 5
2475+
SET @counter=0;
2476+
SELECT ROW(@f1:=f1(),1) = ROW(1,1) AS eq, @counter AS counter FROM seq_1_to_5;
2477+
eq counter
2478+
1 1
2479+
0 2
2480+
0 3
2481+
0 4
2482+
0 5
2483+
SET @counter=0;
2484+
SELECT ROW(f1(),1) IN ((1,1),(1,2)) AS c0, @counter AS counter FROM seq_1_to_5;
2485+
c0 counter
2486+
1 1
2487+
0 2
2488+
0 3
2489+
0 4
2490+
0 5
2491+
DROP FUNCTION f1;
2492+
#
2493+
# Queries with comparison of an SP returning ROW vs a ROW constant.
2494+
# Item_func_sp::bring_value() is called on the left side,
2495+
# which calls execute().
2496+
#
2497+
CREATE FUNCTION f1() RETURNS ROW (a INT,b VARCHAR(10))
2498+
BEGIN
2499+
SET @counter= COALESCE(@counter, 0) + 1;
2500+
RETURN ROW(1,'b1');
2501+
END;
2502+
/
2503+
SET @counter=0;
2504+
SELECT f1() = ROW(1,'b1') AS eq, @counter AS counter FROM seq_1_to_5;
2505+
eq counter
2506+
1 1
2507+
1 2
2508+
1 3
2509+
1 4
2510+
1 5
2511+
DROP FUNCTION f1;
2512+
# End of 11.8 tests

mysql-test/main/sp-row.test

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
--source include/have_sequence.inc
2+
13
--echo #
24
--echo # MDEV-10914 ROW data type for stored routine variables
35
--echo #
@@ -1594,3 +1596,102 @@ DROP PROCEDURE p1;
15941596
--echo #
15951597
--echo # End of 11.7 tests
15961598
--echo #
1599+
1600+
1601+
--echo #
1602+
--echo # MDEV-36322 Comparison ROW(stored_func(),1)=ROW(1,1) calls the function twice per row
1603+
--echo #
1604+
1605+
--disable_ps2_protocol
1606+
1607+
DELIMITER /;
1608+
CREATE FUNCTION f1() RETURNS INT
1609+
BEGIN
1610+
SET @counter= COALESCE(@counter, 0) + 1;
1611+
RETURN @counter;
1612+
END;
1613+
/
1614+
DELIMITER ;/
1615+
1616+
1617+
--echo #
1618+
--echo # Queries without ROW comparison
1619+
--echo #
1620+
1621+
1622+
SET @counter=0;
1623+
SELECT f1() FROM seq_1_to_5;
1624+
1625+
--disable_view_protocol
1626+
SET @counter=0;
1627+
SELECT f1() AS f FROM seq_1_to_5 ORDER BY f;
1628+
1629+
SET @counter=0;
1630+
SELECT f1() AS f FROM seq_1_to_5 ORDER BY f DESC;
1631+
--enable_view_protocol
1632+
1633+
SET @counter=0;
1634+
SELECT f1()=1 AS eq, @counter AS counter FROM seq_1_to_5;
1635+
1636+
--echo #
1637+
--echo # Queries without ROW comparison + HAVING
1638+
--echo # The counter is incremented by 2 per row.
1639+
--echo #
1640+
1641+
SET @counter=0;
1642+
SELECT f1() AS f FROM seq_1_to_5 HAVING f1()<>0;
1643+
1644+
SET @counter=0;
1645+
SELECT f1() AS f FROM seq_1_to_5 HAVING f<>0;
1646+
1647+
1648+
--echo #
1649+
--echo # Queries with ROW comparison.
1650+
--echo # Item_row::bring_value() is called on the left side, which calls
1651+
--echo # Item_func_sp::bring_value() for f1(),
1652+
--echo # which does *not* call Item_func_sp::execute()
1653+
--echo # because the return type of f1() is scalar.
1654+
--echo # Item_func_sp::execute() will be called from Item_func_sp::val_int()
1655+
--echo # from Arg_comparator::compare_int_signed().
1656+
--echo #
1657+
1658+
SET @counter=0;
1659+
SELECT ROW(f1(),1) = ROW(1,1) AS eq, @counter AS counter FROM seq_1_to_5;
1660+
1661+
SET @counter=0;
1662+
SELECT ROW(COALESCE(f1()),1) = ROW(1,1) AS eq, @counter AS counter FROM seq_1_to_5;
1663+
1664+
SET @counter=0;
1665+
SELECT ROW(@f1:=f1(),1) = ROW(1,1) AS eq, @counter AS counter FROM seq_1_to_5;
1666+
1667+
SET @counter=0;
1668+
SELECT ROW(f1(),1) IN ((1,1),(1,2)) AS c0, @counter AS counter FROM seq_1_to_5;
1669+
1670+
DROP FUNCTION f1;
1671+
1672+
1673+
--echo #
1674+
--echo # Queries with comparison of an SP returning ROW vs a ROW constant.
1675+
--echo # Item_func_sp::bring_value() is called on the left side,
1676+
--echo # which calls execute().
1677+
--echo #
1678+
1679+
DELIMITER /;
1680+
CREATE FUNCTION f1() RETURNS ROW (a INT,b VARCHAR(10))
1681+
BEGIN
1682+
SET @counter= COALESCE(@counter, 0) + 1;
1683+
RETURN ROW(1,'b1');
1684+
END;
1685+
/
1686+
DELIMITER ;/
1687+
1688+
--disable_view_protocol
1689+
SET @counter=0;
1690+
SELECT f1() = ROW(1,'b1') AS eq, @counter AS counter FROM seq_1_to_5;
1691+
--enable_view_protocol
1692+
1693+
DROP FUNCTION f1;
1694+
1695+
--enable_ps2_protocol
1696+
1697+
--echo # End of 11.8 tests

sql/item.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2530,7 +2530,17 @@ class Item :public Value_source,
25302530
bool check_type_can_return_time(const LEX_CSTRING &opname) const;
25312531
// It is not row => null inside is impossible
25322532
virtual bool null_inside() { return 0; }
2533-
// used in row subselects to get value of elements
2533+
/*
2534+
bring_value()
2535+
- For scalar Item types this method does not do anything.
2536+
- For Items which can be of the ROW data type,
2537+
this method brings the row, so its component values become available
2538+
for calling their value methods (such as val_int(), get_date() etc).
2539+
* Item_singlerow_subselect stores component values in
2540+
the array of Item_cache in Item_singlerow_subselect::row.
2541+
* Item_func_sp stores component values in Field_row::m_table
2542+
of the Field_row instance pointed by Item_func_sp::sp_result_field.
2543+
*/
25342544
virtual void bring_value() {}
25352545

25362546
const Type_handler *type_handler_long_or_longlong() const

sql/item_func.h

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4023,7 +4023,79 @@ class Item_func_sp :public Item_func,
40234023

40244024
void bring_value() override
40254025
{
4026-
execute();
4026+
DBUG_ASSERT(fixed());
4027+
/*
4028+
This comment describes the difference between a single row
4029+
subselect and a stored function returning ROW.
4030+
4031+
In case of a single column subselect:
4032+
SELECT 1=(SELECT a FROM t1) FROM seq_1_to_5;
4033+
Item_singlerow_subselect pretends to be a scalar,
4034+
so its type_handler() returns the type handler of the column "a".
4035+
(*) This is according to the SQL scandard, which says:
4036+
The declared type of a <scalar subquery> is the declared
4037+
type of the column of QE (i.e. its query expression).
4038+
In the above SELECT statement Arg_comparator calls a scalar comparison
4039+
function e.g. compare_int_signed(), which does not call bring_value().
4040+
Item_singlerow_subselect::exec() is called when
4041+
Arg_comparator::compare_int_signed(), or another scalar comparison
4042+
function, calls a value method like Item_singlerow_subselect::val_int().
4043+
4044+
In case of a multiple-column subselect:
4045+
SELECT (1,1)=(SELECT a,a FROM t1) FROM seq_1_to_5;
4046+
Item_singlerow_subselect::type_handler() returns &type_handler_row.
4047+
Arg_comparator uses compare_row() to compare its arguments.
4048+
compare_row() calls bring_value(), which calls
4049+
Item_singlerow_subselect::exec().
4050+
4051+
Unlike a single row subselect, a stored function returning a ROW does
4052+
not pretend to be a scalar when there is only one column in the ROW:
4053+
SELECT sp_row_func_with_one_col()=sp_row_var_with_one_col FROM ...;
4054+
Item_function_sp::type_handler() still returns &type_handler_row when
4055+
the return type is a ROW with one column.
4056+
Arg_comparator choses compare_row() as the comparison function.
4057+
So the execution comes to here.
4058+
4059+
This chart summarizes how a comparison of ROW values works.
4060+
In particular, how Item_singlerow_subselect::exec() vs
4061+
Item_func_sp::execute() are called.
4062+
4063+
Single row subselect ROW value stored function
4064+
-------------------- -------------------------
4065+
1. bring_value() Yes Yes
4066+
is called when
4067+
cols>1
4068+
2. exec()/execute() Yes Yes
4069+
is called from
4070+
bring_value()
4071+
when cols>1
4072+
3. Pretends Yes No
4073+
to be a scalar
4074+
when cols==1
4075+
4. bring_value() No Yes
4076+
is called
4077+
when cols==1
4078+
5. exec()/execute() N/A No
4079+
is called from
4080+
bring_value()
4081+
when cols==1
4082+
6. exec()/execute() Yes Yes
4083+
is called from
4084+
a value method,
4085+
like val_int()
4086+
when cols==1
4087+
*/
4088+
if (result_type() == ROW_RESULT)
4089+
{
4090+
/*
4091+
The condition in the "if" above catches the *intentional* difference
4092+
in the chart lines 3,4,5 (between a single row subselect and a stored
4093+
function returning ROW). Thus the condition makes #6 work in the same
4094+
way. See (*) in the beginning of the comment why the difference is
4095+
intentional.
4096+
*/
4097+
execute();
4098+
}
40274099
}
40284100

40294101
Field *create_tmp_field_ex(MEM_ROOT *root, TABLE *table, Tmp_field_src *src,

0 commit comments

Comments
 (0)