1
0
mirror of https://github.com/MariaDB/server.git synced 2025-07-27 18:02:13 +03:00

MDEV-32275 getting error 'Illegal parameter data types row and bigint for operation '+' ' when using ITERATE in a FOR..DO

An "ITERATE innerLoop" did not work properly inside
a WHILE loop, which itself is inside an outer FOR loop:

outerLoop:
  FOR
   ...
   innerLoop:
    WHILE
      ...
      ITERATE innerLoop;
      ...
    END WHILE;
    ...
  END FOR;

It erroneously generated an integer increment code for the outer FOR loop.
There were two problems:
1. "ITERATE innerLoop" worked like "ITERATE outerLoop"
2. It was always integer increment, even in case of FOR cursor loops.

Background:
- A FOR loop automatically creates a dedicated sp_pcontext stack entry,
  to put the iteration and bound variables on it.

- Other loop types (LOOP, WHILE, REPEAT), do not generate a dedicated
  slack entry.

  The old code erroneously assumed that sp_pcontext::m_for_loop
  either describes the most inner loop (in case the inner loop is FOR),
  or is empty (in case the inner loop is not FOR).

  But in fact, sp_pcontext::m_for_loop is never empty inside a FOR loop:
  it describes the closest FOR loop, even if this FOR loop has nested
  non-FOR loops inside.

  So when we're near the ITERATE statement in the above script,
  sp_pcontext::m_for_loop is not empty - it stores information about
  the FOR loop labeled as "outrLoop:".

Fix:
- Adding a new member sp_pcontext::Lex_for_loop::m_start_label,
  to remember the explicit or the auto-generated label correspoding
  to the start of the FOR body. It's used during generation
  of "ITERATE loop_label" code to check if "loop_label" belongs
  to the current FOR loop pointed by sp_pcontext::m_for_loop,
  or belongs to a non-FOR nested loop.

- Adding LEX methods sp_for_loop_intrange_iterate() and
  sp_for_loop_cursor_iterate() to reuse the code between
  methods handling:
  * ITERATE
  * END FOR

- Adding a test for Lex_for_loop::is_for_loop_cursor()
  and generate a code either a cursor fetch, or for an integer increment.
  Before this change, it always erroneously generated an integer increment
  version.

- Cleanup: Initialize Lex_for_loop_st::m_cursor_offset inside
  Lex_for_loop_st::init(), to avoid not initialized members.

- Cleanup: Removing a redundant method:
    Lex_for_loop_st::init(const Lex_for_loop_st &other)
  Using Lex_for_loop_st::operator(const Lex_for_loop_st &other) instead.
This commit is contained in:
Alexander Barkov
2023-10-04 14:22:02 +04:00
parent e2da748c29
commit 534a2bf1c6
8 changed files with 672 additions and 24 deletions

View File

@ -1356,3 +1356,330 @@ drop function f1;
#
# End of 10.3 tests
#
#
# Start of 10.4 tests
#
#
# MDEV-32275 getting error 'Illegal parameter data types row and bigint for operation '+' ' when using ITERATE in a FOR..DO
#
#
# Unlabeled FOR/cursor with a nested labeled LOOP inside
#
CREATE OR REPLACE PROCEDURE p1()
BEGIN
DECLARE loopDone TINYINT DEFAULT FALSE;
FOR _row IN (SELECT '' AS a) DO
SELECT 'start of outerLoop';
innerLoop: LOOP
SELECT 'start of innerLoop';
IF loopDone THEN
LEAVE innerLoop;
END IF;
SET loopDone = TRUE;
IF _row.a = 'v1'
THEN
SELECT 'start of THEN block';
ITERATE innerLoop;
END IF;
SELECT 'end of innerLoop';
END LOOP;
SELECT 'end of outerLoop';
END FOR;
END
$$
SHOW PROCEDURE CODE p1;
Pos Instruction
0 set loopDone@0 0
1 cpush [implicit_cursor]@0
2 cursor_copy_struct [implicit_cursor] _row@1
3 copen [implicit_cursor]@0
4 cfetch [implicit_cursor]@0 _row@1
5 jump_if_not 19(19) `[implicit_cursor]`%FOUND
6 stmt 0 "SELECT 'start of outerLoop'"
7 stmt 0 "SELECT 'start of innerLoop'"
8 jump_if_not 10(10) loopDone@0
9 jump 16
10 set loopDone@0 1
11 jump_if_not 14(14) _row.a@1["a"] = 'v1'
12 stmt 0 "SELECT 'start of THEN block'"
13 jump 7
14 stmt 0 "SELECT 'end of innerLoop'"
15 jump 7
16 stmt 0 "SELECT 'end of outerLoop'"
17 cfetch [implicit_cursor]@0 _row@1
18 jump 5
19 cpop 1
DROP PROCEDURE p1;
#
# Labeled FOR/cursor with a nested labeled LOOP
#
CREATE OR REPLACE PROCEDURE p1()
BEGIN
DECLARE loopDone TINYINT DEFAULT FALSE;
outerLoop:
FOR _row IN (SELECT '' AS a) DO
SELECT 'start of outerLoop';
innerLoop: LOOP
SELECT 'start of innerLoop';
IF loopDone THEN
LEAVE innerLoop;
END IF;
SET loopDone = TRUE;
IF _row.a = 'v1'
THEN
SELECT 'start of IF/v1/THEN block';
ITERATE innerLoop;
END IF;
IF _row.a = 'v2'
THEN
SELECT 'start of IF/v2/THEN block';
ITERATE outerLoop;
END IF;
SELECT 'end of innerLoop';
END LOOP;
SELECT 'end of outerLoop';
END FOR;
END
$$
SHOW PROCEDURE CODE p1;
Pos Instruction
0 set loopDone@0 0
1 cpush [implicit_cursor]@0
2 cursor_copy_struct [implicit_cursor] _row@1
3 copen [implicit_cursor]@0
4 cfetch [implicit_cursor]@0 _row@1
5 jump_if_not 23(23) `[implicit_cursor]`%FOUND
6 stmt 0 "SELECT 'start of outerLoop'"
7 stmt 0 "SELECT 'start of innerLoop'"
8 jump_if_not 10(10) loopDone@0
9 jump 20
10 set loopDone@0 1
11 jump_if_not 14(14) _row.a@1["a"] = 'v1'
12 stmt 0 "SELECT 'start of IF/v1/THEN block'"
13 jump 7
14 jump_if_not 18(18) _row.a@1["a"] = 'v2'
15 stmt 0 "SELECT 'start of IF/v2/THEN block'"
16 cfetch [implicit_cursor]@0 _row@1
17 jump 5
18 stmt 0 "SELECT 'end of innerLoop'"
19 jump 7
20 stmt 0 "SELECT 'end of outerLoop'"
21 cfetch [implicit_cursor]@0 _row@1
22 jump 5
23 cpop 1
DROP PROCEDURE p1;
#
# Unlabeled FOR/integer with a labeled LOOP inside
#
CREATE OR REPLACE PROCEDURE p1()
BEGIN
DECLARE loopDone TINYINT DEFAULT FALSE;
FOR _index IN 1..10 DO
SELECT 'start of outerLoop';
innerLoop: LOOP
SELECT 'start of innerLoop';
IF loopDone THEN
LEAVE innerLoop;
END IF;
SET loopDone = TRUE;
IF _index = 1
THEN
SELECT 'start of THEN block';
ITERATE innerLoop;
END IF;
SELECT 'end of innerLoop';
END LOOP;
SELECT 'end of outerLoop';
END FOR;
END
$$
SHOW PROCEDURE CODE p1;
Pos Instruction
0 set loopDone@0 0
1 set _index@1 1
2 set [target_bound]@2 10
3 jump_if_not 19(19) _index@1 <= [target_bound]@2
4 stmt 0 "SELECT 'start of outerLoop'"
5 stmt 0 "SELECT 'start of innerLoop'"
6 jump_if_not 8(8) loopDone@0
7 jump 14
8 set loopDone@0 1
9 jump_if_not 12(12) _index@1 = 1
10 stmt 0 "SELECT 'start of THEN block'"
11 jump 5
12 stmt 0 "SELECT 'end of innerLoop'"
13 jump 5
14 stmt 0 "SELECT 'end of outerLoop'"
15 set _index@1 _index@1 + 1
16 jump 3
DROP PROCEDURE p1;
#
# Labeled FOR/integer with a labeled LOOP inside
#
CREATE OR REPLACE PROCEDURE p1()
BEGIN
DECLARE loopDone TINYINT DEFAULT FALSE;
outerLoop:
FOR _index IN 1..10 DO
SELECT 'start of outerLoop';
innerLoop: LOOP
SELECT 'start of innerLoop';
IF loopDone THEN
LEAVE innerLoop;
END IF;
SET loopDone = TRUE;
IF _index = 1
THEN
SELECT 'start of IF/1/THEN block';
ITERATE innerLoop;
END IF;
IF _index = 2
THEN
SELECT 'start of IF/2/THEN block';
ITERATE outerLoop;
END IF;
SELECT 'end of innerLoop';
END LOOP;
SELECT 'end of outerLoop';
END FOR;
END
$$
SHOW PROCEDURE CODE p1;
Pos Instruction
0 set loopDone@0 0
1 set _index@1 1
2 set [target_bound]@2 10
3 jump_if_not 24(24) _index@1 <= [target_bound]@2
4 stmt 0 "SELECT 'start of outerLoop'"
5 stmt 0 "SELECT 'start of innerLoop'"
6 jump_if_not 8(8) loopDone@0
7 jump 18
8 set loopDone@0 1
9 jump_if_not 12(12) _index@1 = 1
10 stmt 0 "SELECT 'start of IF/1/THEN block'"
11 jump 5
12 jump_if_not 16(16) _index@1 = 2
13 stmt 0 "SELECT 'start of IF/2/THEN block'"
14 set _index@1 _index@1 + 1
15 jump 3
16 stmt 0 "SELECT 'end of innerLoop'"
17 jump 5
18 stmt 0 "SELECT 'end of outerLoop'"
19 set _index@1 _index@1 + 1
20 jump 3
DROP PROCEDURE p1;
#
# Unlabeled FOR/integer with a labeled FOR inside
#
CREATE OR REPLACE PROCEDURE p1()
BEGIN
DECLARE loopDone TINYINT DEFAULT FALSE;
FOR _index_outer IN 1..10 DO
SELECT 'start of outerLoop';
innerLoop:
FOR _index_inner IN 1..10 DO
SELECT 'start of innerLoop';
IF loopDone THEN
LEAVE innerLoop;
END IF;
SET loopDone = TRUE;
IF _index_inner = 1
THEN
SELECT 'start of IF/1/THEN block';
ITERATE innerLoop;
END IF;
SELECT 'end of innerLoop';
END FOR;
SELECT 'end of outerLoop';
END FOR;
END
$$
SHOW PROCEDURE CODE p1;
Pos Instruction
0 set loopDone@0 0
1 set _index_outer@1 1
2 set [target_bound]@2 10
3 jump_if_not 24(24) _index_outer@1 <= [target_bound]@2
4 stmt 0 "SELECT 'start of outerLoop'"
5 set _index_inner@3 1
6 set [target_bound]@4 10
7 jump_if_not 19(19) _index_inner@3 <= [target_bound]@4
8 stmt 0 "SELECT 'start of innerLoop'"
9 jump_if_not 11(11) loopDone@0
10 jump 19
11 set loopDone@0 1
12 jump_if_not 16(16) _index_inner@3 = 1
13 stmt 0 "SELECT 'start of IF/1/THEN block'"
14 set _index_inner@3 _index_inner@3 + 1
15 jump 7
16 stmt 0 "SELECT 'end of innerLoop'"
17 set _index_inner@3 _index_inner@3 + 1
18 jump 7
19 stmt 0 "SELECT 'end of outerLoop'"
20 set _index_outer@1 _index_outer@1 + 1
21 jump 3
DROP PROCEDURE p1;
#
# Labeled FOR/integer with a labeled FOR inside
#
CREATE OR REPLACE PROCEDURE p1()
BEGIN
DECLARE loopDone TINYINT DEFAULT FALSE;
outerLoop:
FOR _index_outer IN 1..10 DO
SELECT 'start of outerLoop';
innerLoop:
FOR _index_inner IN 1..10 DO
SELECT 'start of innerLoop';
IF loopDone THEN
LEAVE innerLoop;
END IF;
SET loopDone = TRUE;
IF _index_inner = 1
THEN
SELECT 'start of IF/1/THEN block';
ITERATE innerLoop;
END IF;
IF _index_inner = 2
THEN
SELECT 'start of IF/2/THEN block';
ITERATE outerLoop;
END IF;
SELECT 'end of innerLoop';
END FOR;
SELECT 'end of outerLoop';
END FOR;
END
$$
SHOW PROCEDURE CODE p1;
Pos Instruction
0 set loopDone@0 0
1 set _index_outer@1 1
2 set [target_bound]@2 10
3 jump_if_not 29(29) _index_outer@1 <= [target_bound]@2
4 stmt 0 "SELECT 'start of outerLoop'"
5 set _index_inner@3 1
6 set [target_bound]@4 10
7 jump_if_not 23(23) _index_inner@3 <= [target_bound]@4
8 stmt 0 "SELECT 'start of innerLoop'"
9 jump_if_not 11(11) loopDone@0
10 jump 23
11 set loopDone@0 1
12 jump_if_not 16(16) _index_inner@3 = 1
13 stmt 0 "SELECT 'start of IF/1/THEN block'"
14 set _index_inner@3 _index_inner@3 + 1
15 jump 7
16 jump_if_not 20(20) _index_inner@3 = 2
17 stmt 0 "SELECT 'start of IF/2/THEN block'"
18 set _index_outer@1 _index_outer@1 + 1
19 jump 3
20 stmt 0 "SELECT 'end of innerLoop'"
21 set _index_inner@3 _index_inner@3 + 1
22 jump 7
23 stmt 0 "SELECT 'end of outerLoop'"
24 set _index_outer@1 _index_outer@1 + 1
25 jump 3
DROP PROCEDURE p1;
#
# End of 10.4 tests
#