1
0
mirror of https://github.com/MariaDB/server.git synced 2026-10-23 18:49:29 +03:00

MDEV-41243, MDEV-41273 Don't freeze mem_root after engine pushdown

When a statement is pushed down to an engine, the once-per-statement
part of the optimization (e.g. the first_cond_optimization part of
JOIN::optimize(), or the whole of JOIN::optimize() for a pushed down
UNION) is skipped. Re-executing the same prepared statement or stored
routine without pushdown then allocated from a mem_root already marked
ROOT_FLAG_READ_ONLY, hitting an assertion in alloc_root().

Add LEX::dont_freeze_mem_root, set by the pushdown_handler and
derived_handler constructors so it covers every engine implementing
them. Prepared_statement::execute_loop(), sp_head::execute() (through
sp_head::dont_freeze_mem_root) and the SP instruction reparse path no
longer freeze the mem_root of such statements. The flag is only
declared and used in PROTECT_STATEMENT_MEMROOT builds.

The flag is per execution for prepared statements:
Prepared_statement::execute_loop() clears it after each execution, so
the mem_root is frozen again after the first later execution that is
not pushed down. For a stored routine the instruction's LEX keeps the
flag, so once one instruction has been pushed down the routine's
mem_root stays unfrozen, even if later executions are not pushed down,
unless the routine is re-parsed into a new LEX.

Add tests for prepared SELECT, UNION, UPDATE and DELETE, prepared
EXPLAIN of a pushed UNION, derived tables, stored procedures,
functions, triggers, cursors and metadata-invalidation reparse, with
pushdown switched on and off. Most prepared statement and routine
tests check the statements received by the remote server, or EXPLAIN,
to verify that pushdown actually happened.
This commit is contained in:
bsrikanth-mariadb
2026-10-02 17:39:46 +05:30
parent 2db22df805
commit 999b11bd02
13 changed files with 1248 additions and 8 deletions

View File

@@ -1050,6 +1050,708 @@ a c
DROP VIEW v4, v3, v2, v1;
connection master;
set global federated_pushdown=0;
#
# MDEV-41243, MDEV-41273: Assertion during execution of update and
# delete prepared statements with federatedx_pushdown
#
connection master;
CALL fill_tables();
SET global federated_pushdown=1;
PREPARE pstmt FROM "UPDATE t1 AS x JOIN t2 AS y ON x.a= y.a SET x.b= y.c WHERE y.a= 1";
affected rows: 0
info: Statement prepared
# This statement text is eligible for pushdown (PUSHED UPDATE)
EXPLAIN UPDATE t1 AS x JOIN t2 AS y ON x.a= y.a SET x.b= y.c WHERE y.a= 1;
id select_type table type possible_keys key key_len ref rows Extra
1 PUSHED UPDATE NULL NULL NULL NULL NULL NULL NULL NULL
connection slave;
TRUNCATE TABLE mysql.general_log;
SET global general_log=1;
connection master;
# ... and EXECUTE pstmt itself sends it to the remote server as one statement
EXECUTE pstmt;
affected rows: 1
info: Rows matched: 1 Changed: 1 Warnings: 0
connection slave;
SET global general_log=0;
SELECT argument FROM mysql.general_log
WHERE argument LIKE 'update %';
argument
update (`federated`.`t1` `x` join `federated`.`t2` `y` on(`federated`.`x`.`a` = `federated`.`y`.`a`)) set `federated`.`x`.`b` = `federated`.`y`.`c` where `federated`.`y`.`a` = 1
connection master;
SET global federated_pushdown=0;
# Reset the data so the second, non-pushed-down EXECUTE actually
# changes a row instead of silently matching but not changing one
CALL fill_tables();
# This statement text is no longer eligible for pushdown
EXPLAIN UPDATE t1 AS x JOIN t2 AS y ON x.a= y.a SET x.b= y.c WHERE y.a= 1;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE y ALL NULL NULL NULL NULL 3 Using where
1 SIMPLE x ALL NULL NULL NULL NULL 4 Using where
connection slave;
TRUNCATE TABLE mysql.general_log;
SET global general_log=1;
connection master;
# ... and EXECUTE pstmt now runs it row by row against the remote server
EXECUTE pstmt;
affected rows: 1
info: Rows matched: 1 Changed: 1 Warnings: 0
connection slave;
SET global general_log=0;
SELECT argument FROM mysql.general_log
WHERE argument LIKE 'update %';
argument
UPDATE `t1` SET `b` = 'I' WHERE `a` = 1 AND `b` = 'one' LIMIT 1
connection master;
# execute the statement with pushdown again
CALL fill_tables();
SET global federated_pushdown=1;
# This statement text is eligible for pushdown (PUSHED UPDATE)
EXPLAIN UPDATE t1 AS x JOIN t2 AS y ON x.a= y.a SET x.b= y.c WHERE y.a= 1;
id select_type table type possible_keys key key_len ref rows Extra
1 PUSHED UPDATE NULL NULL NULL NULL NULL NULL NULL NULL
connection slave;
TRUNCATE TABLE mysql.general_log;
SET global general_log=1;
connection master;
# ... and EXECUTE pstmt itself sends it to the remote server as one statement
EXECUTE pstmt;
affected rows: 1
info: Rows matched: 1 Changed: 1 Warnings: 0
connection slave;
SET global general_log=0;
SELECT argument FROM mysql.general_log
WHERE argument LIKE 'update %';
argument
update `federated`.`t1` `x` join `federated`.`t2` `y` set `federated`.`x`.`b` = `federated`.`y`.`c` where `federated`.`y`.`a` = 1 and `federated`.`x`.`a` = `federated`.`y`.`a`
connection master;
DEALLOCATE PREPARE pstmt;
SELECT * FROM t1 ORDER BY a;
a b
1 I
2 two
3 three
4 four
SET global federated_pushdown=1;
PREPARE pstmt FROM "DELETE FROM x USING t1 AS x JOIN t2 AS y ON x.a= y.a WHERE y.a= 2";
affected rows: 0
info: Statement prepared
# This statement text is eligible for pushdown (PUSHED DELETE)
EXPLAIN DELETE FROM x USING t1 AS x JOIN t2 AS y ON x.a= y.a WHERE y.a= 2;
id select_type table type possible_keys key key_len ref rows Extra
1 PUSHED DELETE NULL NULL NULL NULL NULL NULL NULL NULL
connection slave;
TRUNCATE TABLE mysql.general_log;
SET global general_log=1;
connection master;
# ... and EXECUTE pstmt itself sends it to the remote server as one statement
EXECUTE pstmt;
affected rows: 1
connection slave;
SET global general_log=0;
SELECT argument FROM mysql.general_log
WHERE argument LIKE 'delete %';
argument
delete from `x` using (`federated`.`t1` `x` join `federated`.`t2` `y` on(`federated`.`x`.`a` = `federated`.`y`.`a`)) where `federated`.`y`.`a` = 2
connection master;
SET global federated_pushdown=0;
# Reset the data so the second, non-pushed-down EXECUTE actually
# deletes a row instead of silently matching none
CALL fill_tables();
# This statement text is no longer eligible for pushdown
EXPLAIN DELETE FROM x USING t1 AS x JOIN t2 AS y ON x.a= y.a WHERE y.a= 2;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE y ALL NULL NULL NULL NULL 3 Using where
1 SIMPLE x ALL NULL NULL NULL NULL 4 Using where
connection slave;
TRUNCATE TABLE mysql.general_log;
SET global general_log=1;
connection master;
# ... and EXECUTE pstmt now runs it row by row against the remote server
EXECUTE pstmt;
affected rows: 1
connection slave;
SET global general_log=0;
SELECT argument FROM mysql.general_log
WHERE argument LIKE 'delete %';
argument
DELETE FROM `t1` WHERE `a` = 2 AND `b` = 'two' LIMIT 1
connection master;
# execute the statement with pushdown again
CALL fill_tables();
SET global federated_pushdown=1;
# This statement text is eligible for pushdown (PUSHED DELETE)
EXPLAIN DELETE FROM x USING t1 AS x JOIN t2 AS y ON x.a= y.a WHERE y.a= 2;
id select_type table type possible_keys key key_len ref rows Extra
1 PUSHED DELETE NULL NULL NULL NULL NULL NULL NULL NULL
connection slave;
TRUNCATE TABLE mysql.general_log;
SET global general_log=1;
connection master;
# ... and EXECUTE pstmt itself sends it to the remote server as one statement
EXECUTE pstmt;
affected rows: 1
connection slave;
SET global general_log=0;
SELECT argument FROM mysql.general_log
WHERE argument LIKE 'delete %';
argument
delete from `x` using `federated`.`t1` `x` join `federated`.`t2` `y` where `federated`.`y`.`a` = 2 and `federated`.`x`.`a` = `federated`.`y`.`a`
connection master;
DEALLOCATE PREPARE pstmt;
SELECT * FROM t1 ORDER BY a;
a b
1 one
3 three
4 four
#
# The once-per-statement optimization guarded by
# first_cond_optimization is skipped by the same JOIN::optimize()
# branch for a pushed down plain SELECT, not only UPDATE/DELETE, so a
# prepared SELECT toggling pushdown needs the same protection
#
CALL fill_tables();
SET global federated_pushdown=1;
PREPARE pstmt FROM "SELECT * FROM t1 WHERE a < 3";
affected rows: 0
info: Statement prepared
# This statement text is eligible for pushdown (PUSHED SELECT)
EXPLAIN SELECT * FROM t1 WHERE a < 3;
id select_type table type possible_keys key key_len ref rows Extra
1 PUSHED SELECT NULL NULL NULL NULL NULL NULL NULL NULL
connection slave;
TRUNCATE TABLE mysql.general_log;
SET global general_log=1;
connection master;
# ... and EXECUTE pstmt itself sends it to the remote server as one statement
EXECUTE pstmt;
a b
1 one
2 two
affected rows: 2
connection slave;
SET global general_log=0;
SELECT argument FROM mysql.general_log
WHERE argument LIKE 'select %';
argument
select `federated`.`t1`.`a` AS `a`,`federated`.`t1`.`b` AS `b` from `federated`.`t1` where `federated`.`t1`.`a` < 3
connection master;
SET global federated_pushdown=0;
# This statement text is no longer eligible for pushdown
EXPLAIN SELECT * FROM t1 WHERE a < 3;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 ALL NULL NULL NULL NULL 4 Using where
connection slave;
TRUNCATE TABLE mysql.general_log;
SET global general_log=1;
connection master;
# ... and EXECUTE pstmt now scans the remote table locally instead
EXECUTE pstmt;
a b
1 one
2 two
affected rows: 2
connection slave;
SET global general_log=0;
SELECT argument FROM mysql.general_log
WHERE argument LIKE 'select %';
argument
SELECT `a`, `b` FROM `t1`
connection master;
DEALLOCATE PREPARE pstmt;
#
# Whole-unit pushdown: a UNION pushed down as a single statement
#
CALL fill_tables();
SET global federated_pushdown=1;
PREPARE pstmt FROM "SELECT a, b FROM t1 UNION SELECT a, c FROM t2";
affected rows: 0
info: Statement prepared
# This statement text is eligible for pushdown (PUSHED UNION)
EXPLAIN SELECT a, b FROM t1 UNION SELECT a, c FROM t2;
id select_type table type possible_keys key key_len ref rows Extra
NULL PUSHED UNION NULL NULL NULL NULL NULL NULL NULL NULL
connection slave;
TRUNCATE TABLE mysql.general_log;
SET global general_log=1;
connection master;
# ... and EXECUTE pstmt itself sends it to the remote server as one statement
EXECUTE pstmt;
a b
1 one
2 two
3 three
4 four
1 I
2 II
3 III
affected rows: 7
connection slave;
SET global general_log=0;
SELECT argument FROM mysql.general_log
WHERE argument LIKE 'select %';
argument
select `federated`.`t1`.`a` AS `a`,`federated`.`t1`.`b` AS `b` from `federated`.`t1` union select `federated`.`t2`.`a` AS `a`,`federated`.`t2`.`c` AS `c` from `federated`.`t2`
connection master;
SET global federated_pushdown=0;
# This statement text is no longer eligible for pushdown
EXPLAIN SELECT a, b FROM t1 UNION SELECT a, c FROM t2;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 ALL NULL NULL NULL NULL 4
2 UNION t2 ALL NULL NULL NULL NULL 3
NULL UNION RESULT <union1,2> ALL NULL NULL NULL NULL NULL
connection slave;
TRUNCATE TABLE mysql.general_log;
SET global general_log=1;
connection master;
# ... and EXECUTE pstmt now runs it locally instead
EXECUTE pstmt;
a b
1 one
2 two
3 three
4 four
1 I
2 II
3 III
affected rows: 7
connection slave;
SET global general_log=0;
SELECT argument FROM mysql.general_log
WHERE argument LIKE 'select %';
argument
SELECT `a`, `b` FROM `t1`
SELECT `a`, `c` FROM `t2`
connection master;
DEALLOCATE PREPARE pstmt;
#
# A prepared EXPLAIN of a pushed-down UNION, executed with pushdown
# on and then off
#
CALL fill_tables();
SET global federated_pushdown=1;
PREPARE pstmt FROM "EXPLAIN SELECT a, b FROM t1 UNION SELECT a, c FROM t2";
affected rows: 0
info: Statement prepared
EXECUTE pstmt;
id select_type table type possible_keys key key_len ref rows Extra
NULL PUSHED UNION NULL NULL NULL NULL NULL NULL NULL NULL
SET global federated_pushdown=0;
EXECUTE pstmt;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 ALL NULL NULL NULL NULL 4
2 UNION t2 ALL NULL NULL NULL NULL 3
NULL UNION RESULT <union1,2> ALL NULL NULL NULL NULL NULL
DEALLOCATE PREPARE pstmt;
#
# A UNION used as a derived table is optimized differently from a
# top-level pushed unit: its member SELECTs are optimized directly,
# but the pushed down derived table skips the step that would
# otherwise optimize the DISTINCT/ORDER BY/LIMIT wrapper of the UNION
#
CALL fill_tables();
CREATE TABLE tl (a int) ENGINE=MyISAM;
INSERT INTO tl VALUES (1),(2),(3),(4);
SET global federated_pushdown=1;
PREPARE pstmt FROM "SELECT dt.a FROM (SELECT a FROM t1 UNION SELECT a FROM t2) dt JOIN tl ON dt.a= tl.a ORDER BY dt.a";
affected rows: 0
info: Statement prepared
# This statement text is eligible for pushdown (PUSHED DERIVED)
EXPLAIN SELECT dt.a FROM (SELECT a FROM t1 UNION SELECT a FROM t2) dt JOIN tl ON dt.a= tl.a ORDER BY dt.a;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY tl ALL NULL NULL NULL NULL 4 Using where; Using filesort
1 PRIMARY <derived2> eq_ref distinct_key distinct_key 5 federated.tl.a 1
2 PUSHED DERIVED NULL NULL NULL NULL NULL NULL NULL NULL
connection slave;
TRUNCATE TABLE mysql.general_log;
SET global general_log=1;
connection master;
# ... and EXECUTE pstmt itself sends the derived table to the remote server
EXECUTE pstmt;
a
1
2
3
4
affected rows: 4
connection slave;
SET global general_log=0;
SELECT argument FROM mysql.general_log
WHERE argument LIKE 'select %';
argument
select `federated`.`t1`.`a` AS `a` from `federated`.`t1` union select `federated`.`t2`.`a` AS `a` from `federated`.`t2`
connection master;
SET global federated_pushdown=0;
CALL fill_tables();
EXECUTE pstmt;
a
1
2
3
4
DEALLOCATE PREPARE pstmt;
#
# A UNION nested inside a subquery inside a pushed derived table: the
# derived table dt is itself a single (non-union) SELECT
#
CALL fill_tables();
SET global federated_pushdown=1;
PREPARE pstmt FROM "SELECT dt.a FROM (SELECT DISTINCT a FROM t1 WHERE a IN (SELECT a FROM t2 UNION SELECT a FROM t2)) dt JOIN tl ON dt.a= tl.a ORDER BY dt.a";
affected rows: 0
info: Statement prepared
# This statement text is eligible for pushdown (PUSHED DERIVED)
EXPLAIN SELECT dt.a FROM (SELECT DISTINCT a FROM t1 WHERE a IN (SELECT a FROM t2 UNION SELECT a FROM t2)) dt JOIN tl ON dt.a= tl.a ORDER BY dt.a;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY tl ALL NULL NULL NULL NULL 4 Using where; Using filesort
1 PRIMARY <derived2> ref key0 key0 5 federated.tl.a 1
2 PUSHED DERIVED NULL NULL NULL NULL NULL NULL NULL NULL
connection slave;
TRUNCATE TABLE mysql.general_log;
SET global general_log=1;
connection master;
# ... and EXECUTE pstmt itself sends the derived table to the remote server
EXECUTE pstmt;
a
1
2
3
affected rows: 3
connection slave;
SET global general_log=0;
SELECT argument FROM mysql.general_log
WHERE argument LIKE 'select %';
argument
select distinct `federated`.`t1`.`a` AS `a` from `federated`.`t1` where `federated`.`t1`.`a` in (select `federated`.`t2`.`a` from `federated`.`t2` union select `federated`.`t2`.`a` from `federated`.`t2`)
connection master;
SET global federated_pushdown=0;
CALL fill_tables();
EXECUTE pstmt;
a
1
2
3
DEALLOCATE PREPARE pstmt;
DROP TABLE tl;
#
# MDEV-41243, MDEV-41273: same assertion, but for a stored routine's own
# pushed down UPDATE/DELETE, and for one it runs via dynamic SQL
#
# A pushed down UPDATE/DELETE that is one of a routine's own
# instructions, CALLed with pushdown on and then off
CALL fill_tables();
CREATE PROCEDURE p_upd_del()
BEGIN
UPDATE t1 AS x JOIN t2 AS y ON x.a= y.a SET x.b= y.c WHERE y.a= 1;
DELETE FROM x USING t1 AS x JOIN t2 AS y ON x.a= y.a WHERE y.a= 2;
END|
SET global federated_pushdown=1;
# The routine's statements are sent to the remote server pushed down
connection slave;
TRUNCATE TABLE mysql.general_log;
SET global general_log=1;
connection master;
CALL p_upd_del();
connection slave;
SET global general_log=0;
SELECT argument FROM mysql.general_log
WHERE argument LIKE 'update %' OR argument LIKE 'delete %';
argument
update (`federated`.`t1` `x` join `federated`.`t2` `y` on(`federated`.`x`.`a` = `federated`.`y`.`a`)) set `federated`.`x`.`b` = `federated`.`y`.`c` where `federated`.`y`.`a` = 1
delete from `x` using (`federated`.`t1` `x` join `federated`.`t2` `y` on(`federated`.`x`.`a` = `federated`.`y`.`a`)) where `federated`.`y`.`a` = 2
connection master;
SET global federated_pushdown=0;
# Reset the data so this second, non-pushed-down CALL actually
# changes/deletes a row instead of silently matching none
CALL fill_tables();
# Not pushed down: the remote server sees different statements
connection slave;
TRUNCATE TABLE mysql.general_log;
SET global general_log=1;
connection master;
CALL p_upd_del();
connection slave;
SET global general_log=0;
SELECT argument FROM mysql.general_log
WHERE argument LIKE 'update %' OR argument LIKE 'delete %';
argument
UPDATE `t1` SET `b` = 'I' WHERE `a` = 1 AND `b` = 'one' LIMIT 1
DELETE FROM `t1` WHERE `a` = 2 AND `b` = 'two' LIMIT 1
connection master;
# rerun the same routine with pushdown enabled
SET global federated_pushdown=1;
# The routine's statements are sent to the remote server pushed down
connection slave;
TRUNCATE TABLE mysql.general_log;
SET global general_log=1;
connection master;
CALL p_upd_del();
connection slave;
SET global general_log=0;
SELECT argument FROM mysql.general_log
WHERE argument LIKE 'update %' OR argument LIKE 'delete %';
argument
update `federated`.`t1` `x` join `federated`.`t2` `y` set `federated`.`x`.`b` = `federated`.`y`.`c` where `federated`.`y`.`a` = 1 and `federated`.`x`.`a` = `federated`.`y`.`a`
delete from `x` using `federated`.`t1` `x` join `federated`.`t2` `y` where `federated`.`y`.`a` = 2 and `federated`.`x`.`a` = `federated`.`y`.`a`
connection master;
SELECT * FROM t1 ORDER BY a;
a b
1 I
3 three
4 four
#
# A pushed down statement in a routine that is CALLed from another
# routine: the nested routine must not be frozen either
#
CALL fill_tables();
CREATE PROCEDURE p_outer()
BEGIN
CALL p_upd_del();
END|
SET global federated_pushdown=1;
# Nested routine: statements still pushed down
connection slave;
TRUNCATE TABLE mysql.general_log;
SET global general_log=1;
connection master;
CALL p_outer();
connection slave;
SET global general_log=0;
SELECT argument FROM mysql.general_log
WHERE argument LIKE 'update %' OR argument LIKE 'delete %';
argument
update (`federated`.`t1` `x` join `federated`.`t2` `y` on(`federated`.`x`.`a` = `federated`.`y`.`a`)) set `federated`.`x`.`b` = `federated`.`y`.`c` where `federated`.`y`.`a` = 1
delete from `x` using (`federated`.`t1` `x` join `federated`.`t2` `y` on(`federated`.`x`.`a` = `federated`.`y`.`a`)) where `federated`.`y`.`a` = 2
connection master;
SET global federated_pushdown=0;
CALL fill_tables();
CALL p_outer();
SELECT * FROM t1 ORDER BY a;
a b
1 I
3 three
4 four
#
# EXECUTE of a prepared statement inside a routine exercises the
# interaction between sp_head::execute() and
# Prepared_statement::execute_loop(), each of which decides on its own
# whether to freeze its mem_root. The PREPARE stays
# outside the routine so the SAME prepared statement, not a fresh one,
# is EXECUTEd by both CALLs below
#
CALL fill_tables();
PREPARE stmt FROM "UPDATE t1 AS x JOIN t2 AS y ON x.a= y.a SET x.b= y.c WHERE y.a= 1";
CREATE PROCEDURE p_execute()
BEGIN
EXECUTE stmt;
END|
SET global federated_pushdown=1;
# EXECUTE in routine: statement pushed down
connection slave;
TRUNCATE TABLE mysql.general_log;
SET global general_log=1;
connection master;
CALL p_execute();
connection slave;
SET global general_log=0;
SELECT argument FROM mysql.general_log
WHERE argument LIKE 'update %';
argument
update (`federated`.`t1` `x` join `federated`.`t2` `y` on(`federated`.`x`.`a` = `federated`.`y`.`a`)) set `federated`.`x`.`b` = `federated`.`y`.`c` where `federated`.`y`.`a` = 1
connection master;
SET global federated_pushdown=0;
CALL fill_tables();
CALL p_execute();
DEALLOCATE PREPARE stmt;
SELECT * FROM t1 ORDER BY a;
a b
1 I
2 two
3 three
4 four
#
# A static cursor's OPEN has no LEX of its own: it runs the cursor's
# SELECT using the corresponding DECLARE (cpush) instruction's LEX
# instead (see sp_instr_copen::execute())
#
CALL fill_tables();
CREATE PROCEDURE p_cursor()
BEGIN
DECLARE v_a INT;
DECLARE v_b VARCHAR(16);
DECLARE cur CURSOR FOR SELECT a, b FROM t1 WHERE a < 3;
OPEN cur;
FETCH cur INTO v_a, v_b;
CLOSE cur;
END|
SET global federated_pushdown=1;
# Cursor SELECT pushed down
connection slave;
TRUNCATE TABLE mysql.general_log;
SET global general_log=1;
connection master;
CALL p_cursor();
connection slave;
SET global general_log=0;
SELECT argument FROM mysql.general_log
WHERE argument LIKE 'select %';
argument
select `federated`.`t1`.`a` AS `a`,`federated`.`t1`.`b` AS `b` from `federated`.`t1` where `federated`.`t1`.`a` < 3
connection master;
SET global federated_pushdown=0;
CALL fill_tables();
CALL p_cursor();
DROP PROCEDURE p_cursor;
#
# Pushdown off first (the statement is executed without pushdown),
# then on, then off again, for a prepared statement and a routine
#
SET global federated_pushdown=0;
CALL fill_tables();
PREPARE pstmt FROM "UPDATE t1 AS x JOIN t2 AS y ON x.a= y.a SET x.b= y.c WHERE y.a= 1";
CREATE PROCEDURE p_off_on()
UPDATE t1 AS x JOIN t2 AS y ON x.a= y.a SET x.b= y.c WHERE y.a= 1;
EXECUTE pstmt;
CALL p_off_on();
SET global federated_pushdown=1;
CALL fill_tables();
EXECUTE pstmt;
CALL p_off_on();
SET global federated_pushdown=0;
CALL fill_tables();
EXECUTE pstmt;
CALL p_off_on();
SELECT a, b FROM t1 ORDER BY a;
a b
1 I
2 two
3 three
4 four
DEALLOCATE PREPARE pstmt;
DROP PROCEDURE p_off_on;
#
# A pushed down UPDATE/DELETE inside a stored function and inside a
# trigger, executed with pushdown on, then off, then on again
#
CALL fill_tables();
CREATE TABLE tl_trg (a int);
CREATE FUNCTION f_upd_del() RETURNS int
BEGIN
UPDATE t1 AS x JOIN t2 AS y ON x.a= y.a SET x.b= y.c WHERE y.a= 1;
DELETE FROM x USING t1 AS x JOIN t2 AS y ON x.a= y.a WHERE y.a= 2;
RETURN 1;
END|
CREATE TRIGGER trg_upd_del AFTER INSERT ON tl_trg FOR EACH ROW
BEGIN
UPDATE t1 AS x JOIN t2 AS y ON x.a= y.a SET x.b= y.c WHERE y.a= 3;
DELETE FROM x USING t1 AS x JOIN t2 AS y ON x.a= y.a WHERE y.a= 3;
END|
SET global federated_pushdown=1;
# Function: statements pushed down
connection slave;
TRUNCATE TABLE mysql.general_log;
SET global general_log=1;
connection master;
SELECT f_upd_del();
f_upd_del()
1
connection slave;
SET global general_log=0;
SELECT argument FROM mysql.general_log
WHERE argument LIKE 'update %' OR argument LIKE 'delete %';
argument
update (`federated`.`t1` `x` join `federated`.`t2` `y` on(`federated`.`x`.`a` = `federated`.`y`.`a`)) set `federated`.`x`.`b` = `federated`.`y`.`c` where `federated`.`y`.`a` = 1
delete from `x` using (`federated`.`t1` `x` join `federated`.`t2` `y` on(`federated`.`x`.`a` = `federated`.`y`.`a`)) where `federated`.`y`.`a` = 2
connection master;
# Trigger: statements pushed down
connection slave;
TRUNCATE TABLE mysql.general_log;
SET global general_log=1;
connection master;
INSERT INTO tl_trg VALUES (1);
connection slave;
SET global general_log=0;
SELECT argument FROM mysql.general_log
WHERE argument LIKE 'update %' OR argument LIKE 'delete %';
argument
update (`federated`.`t1` `x` join `federated`.`t2` `y` on(`federated`.`x`.`a` = `federated`.`y`.`a`)) set `federated`.`x`.`b` = `federated`.`y`.`c` where `federated`.`y`.`a` = 3
delete from `x` using (`federated`.`t1` `x` join `federated`.`t2` `y` on(`federated`.`x`.`a` = `federated`.`y`.`a`)) where `federated`.`y`.`a` = 3
connection master;
SELECT * FROM t1 ORDER BY a;
a b
1 I
4 four
SET global federated_pushdown=0;
CALL fill_tables();
SELECT f_upd_del();
f_upd_del()
1
INSERT INTO tl_trg VALUES (2);
SELECT * FROM t1 ORDER BY a;
a b
1 I
4 four
SET global federated_pushdown=1;
CALL fill_tables();
SELECT f_upd_del();
f_upd_del()
1
INSERT INTO tl_trg VALUES (3);
SELECT * FROM t1 ORDER BY a;
a b
1 I
4 four
DROP TRIGGER trg_upd_del;
DROP FUNCTION f_upd_del;
DROP TABLE tl_trg;
SET global federated_pushdown=0;
#
# A metadata-invalidation reparse of the routine's own instruction,
# when the first post-reparse execution of that instruction happens
# to be pushed down, must not hit the mem_root assertion
#
CALL fill_tables();
CREATE PROCEDURE p_reparse()
BEGIN
UPDATE t1 AS x JOIN t2 AS y ON x.a= y.a SET x.b= y.c WHERE y.a= 1;
END|
SET global federated_pushdown=1;
CALL p_reparse();
# Force a metadata-invalidation reparse of p_reparse()'s instruction
# on its next CALL. FederatedX doesn't support ALTER TABLE, so drop
# and recreate t2 identically -- that still bumps its table version.
DROP TABLE t2;
CREATE TABLE t2 (a int, c varchar(16))
ENGINE="FEDERATED" DEFAULT CHARSET=latin1
CONNECTION='mysql://root@127.0.0.1:SLAVE_PORT/federated/t2';
CALL fill_tables();
# The first post-reparse execution runs with pushdown on
connection slave;
TRUNCATE TABLE mysql.general_log;
SET global general_log=1;
connection master;
CALL p_reparse();
connection slave;
SET global general_log=0;
SELECT argument FROM mysql.general_log
WHERE argument LIKE 'update %';
argument
update (`federated`.`t1` `x` join `federated`.`t2` `y` on(`federated`.`x`.`a` = `federated`.`y`.`a`)) set `federated`.`x`.`b` = `federated`.`y`.`c` where `federated`.`y`.`a` = 1
connection master;
SET global federated_pushdown=0;
# Reset the data so this non-pushed-down CALL actually changes a row
CALL fill_tables();
# A later, non-pushed-down execution must work too
CALL p_reparse();
SELECT a, b FROM t1 ORDER BY a;
a b
1 I
2 two
3 three
4 four
DROP PROCEDURE p_reparse;
DROP PROCEDURE p_upd_del;
DROP PROCEDURE p_outer;
DROP PROCEDURE p_execute;
DROP PROCEDURE fill_tables;
DROP TABLE federated.t1, federated.t2, federated.t3;
connection slave;

View File

@@ -14,6 +14,7 @@
# from the ordinary row-by-row execution (several "UPDATE ... LIMIT 1"
# statements sent by the ha_federatedx handler methods).
#
--source include/no_protocol.inc
--source have_federatedx.inc
--source include/federated.inc
--source include/no_valgrind_without_big.inc
@@ -777,6 +778,463 @@ DROP VIEW v4, v3, v2, v1;
connection master;
set global federated_pushdown=0;
--echo #
--echo # MDEV-41243, MDEV-41273: Assertion during execution of update and
--echo # delete prepared statements with federatedx_pushdown
--echo #
connection master;
CALL fill_tables();
--let $q= UPDATE t1 AS x JOIN t2 AS y ON x.a= y.a SET x.b= y.c WHERE y.a= 1
SET global federated_pushdown=1;
--enable_info
eval PREPARE pstmt FROM "$q";
--disable_info
--echo # This statement text is eligible for pushdown (PUSHED UPDATE)
eval EXPLAIN $q;
--let $exec_msg= ... and EXECUTE pstmt itself sends it to the remote server as one statement
--let $log_pattern= update %
--source suite/federated/include/federatedx_pushdown_ps_exec_log.inc
SET global federated_pushdown=0;
--echo # Reset the data so the second, non-pushed-down EXECUTE actually
--echo # changes a row instead of silently matching but not changing one
CALL fill_tables();
--echo # This statement text is no longer eligible for pushdown
eval EXPLAIN $q;
--let $exec_msg= ... and EXECUTE pstmt now runs it row by row against the remote server
--let $log_pattern= update %
--source suite/federated/include/federatedx_pushdown_ps_exec_log.inc
--echo # execute the statement with pushdown again
CALL fill_tables();
SET global federated_pushdown=1;
--echo # This statement text is eligible for pushdown (PUSHED UPDATE)
eval EXPLAIN $q;
--let $exec_msg= ... and EXECUTE pstmt itself sends it to the remote server as one statement
--let $log_pattern= update %
--source suite/federated/include/federatedx_pushdown_ps_exec_log.inc
DEALLOCATE PREPARE pstmt;
SELECT * FROM t1 ORDER BY a;
--let $q= DELETE FROM x USING t1 AS x JOIN t2 AS y ON x.a= y.a WHERE y.a= 2
SET global federated_pushdown=1;
--enable_info
eval PREPARE pstmt FROM "$q";
--disable_info
--echo # This statement text is eligible for pushdown (PUSHED DELETE)
eval EXPLAIN $q;
--let $exec_msg= ... and EXECUTE pstmt itself sends it to the remote server as one statement
--let $log_pattern= delete %
--source suite/federated/include/federatedx_pushdown_ps_exec_log.inc
SET global federated_pushdown=0;
--echo # Reset the data so the second, non-pushed-down EXECUTE actually
--echo # deletes a row instead of silently matching none
CALL fill_tables();
--echo # This statement text is no longer eligible for pushdown
eval EXPLAIN $q;
--let $exec_msg= ... and EXECUTE pstmt now runs it row by row against the remote server
--let $log_pattern= delete %
--source suite/federated/include/federatedx_pushdown_ps_exec_log.inc
--echo # execute the statement with pushdown again
CALL fill_tables();
SET global federated_pushdown=1;
--echo # This statement text is eligible for pushdown (PUSHED DELETE)
eval EXPLAIN $q;
--let $exec_msg= ... and EXECUTE pstmt itself sends it to the remote server as one statement
--let $log_pattern= delete %
--source suite/federated/include/federatedx_pushdown_ps_exec_log.inc
DEALLOCATE PREPARE pstmt;
SELECT * FROM t1 ORDER BY a;
--echo #
--echo # The once-per-statement optimization guarded by
--echo # first_cond_optimization is skipped by the same JOIN::optimize()
--echo # branch for a pushed down plain SELECT, not only UPDATE/DELETE, so a
--echo # prepared SELECT toggling pushdown needs the same protection
--echo #
--let $q= SELECT * FROM t1 WHERE a < 3
CALL fill_tables();
SET global federated_pushdown=1;
--enable_info
eval PREPARE pstmt FROM "$q";
--disable_info
--echo # This statement text is eligible for pushdown (PUSHED SELECT)
eval EXPLAIN $q;
--let $exec_msg= ... and EXECUTE pstmt itself sends it to the remote server as one statement
--let $log_pattern= select %
--source suite/federated/include/federatedx_pushdown_ps_exec_log.inc
SET global federated_pushdown=0;
--echo # This statement text is no longer eligible for pushdown
eval EXPLAIN $q;
--let $exec_msg= ... and EXECUTE pstmt now scans the remote table locally instead
--let $log_pattern= select %
--source suite/federated/include/federatedx_pushdown_ps_exec_log.inc
DEALLOCATE PREPARE pstmt;
--echo #
--echo # Whole-unit pushdown: a UNION pushed down as a single statement
--echo #
# This takes a completely different code path from single-SELECT/UPDATE/
# DELETE pushdown: st_select_lex_unit::exec() returns
# pushdown_unit->execute() directly, without ever calling
# optimize()/exec_inner(), so JOIN::optimize() never runs for any member
# SELECT either. The pushdown_handler constructor sets
# LEX::dont_freeze_mem_root on the statement's LEX.
--let $q= SELECT a, b FROM t1 UNION SELECT a, c FROM t2
CALL fill_tables();
SET global federated_pushdown=1;
--enable_info
eval PREPARE pstmt FROM "$q";
--disable_info
--echo # This statement text is eligible for pushdown (PUSHED UNION)
eval EXPLAIN $q;
--let $exec_msg= ... and EXECUTE pstmt itself sends it to the remote server as one statement
--let $log_pattern= select %
--source suite/federated/include/federatedx_pushdown_ps_exec_log.inc
SET global federated_pushdown=0;
--echo # This statement text is no longer eligible for pushdown
eval EXPLAIN $q;
--let $exec_msg= ... and EXECUTE pstmt now runs it locally instead
--let $log_pattern= select %
--source suite/federated/include/federatedx_pushdown_ps_exec_log.inc
DEALLOCATE PREPARE pstmt;
--echo #
--echo # A prepared EXPLAIN of a pushed-down UNION, executed with pushdown
--echo # on and then off
--echo #
--let $q= EXPLAIN SELECT a, b FROM t1 UNION SELECT a, c FROM t2
CALL fill_tables();
SET global federated_pushdown=1;
--enable_info
eval PREPARE pstmt FROM "$q";
--disable_info
EXECUTE pstmt;
SET global federated_pushdown=0;
EXECUTE pstmt;
DEALLOCATE PREPARE pstmt;
--echo #
--echo # A UNION used as a derived table is optimized differently from a
--echo # top-level pushed unit: its member SELECTs are optimized directly,
--echo # but the pushed down derived table skips the step that would
--echo # otherwise optimize the DISTINCT/ORDER BY/LIMIT wrapper of the UNION
--echo #
CALL fill_tables();
CREATE TABLE tl (a int) ENGINE=MyISAM;
INSERT INTO tl VALUES (1),(2),(3),(4);
SET global federated_pushdown=1;
--enable_info
PREPARE pstmt FROM "SELECT dt.a FROM (SELECT a FROM t1 UNION SELECT a FROM t2) dt JOIN tl ON dt.a= tl.a ORDER BY dt.a";
--disable_info
--echo # This statement text is eligible for pushdown (PUSHED DERIVED)
EXPLAIN SELECT dt.a FROM (SELECT a FROM t1 UNION SELECT a FROM t2) dt JOIN tl ON dt.a= tl.a ORDER BY dt.a;
--let $exec_msg= ... and EXECUTE pstmt itself sends the derived table to the remote server
--let $log_pattern= select %
--source suite/federated/include/federatedx_pushdown_ps_exec_log.inc
SET global federated_pushdown=0;
CALL fill_tables();
EXECUTE pstmt;
DEALLOCATE PREPARE pstmt;
--echo #
--echo # A UNION nested inside a subquery inside a pushed derived table: the
--echo # derived table dt is itself a single (non-union) SELECT
--echo #
# dt itself is fully optimized, so what is skipped when it is pushed down
# is the exec_inner()/fake_select_lex optimization of the IN-subquery's
# UNION, as that subquery is never run locally.
CALL fill_tables();
SET global federated_pushdown=1;
--enable_info
PREPARE pstmt FROM "SELECT dt.a FROM (SELECT DISTINCT a FROM t1 WHERE a IN (SELECT a FROM t2 UNION SELECT a FROM t2)) dt JOIN tl ON dt.a= tl.a ORDER BY dt.a";
--disable_info
--echo # This statement text is eligible for pushdown (PUSHED DERIVED)
EXPLAIN SELECT dt.a FROM (SELECT DISTINCT a FROM t1 WHERE a IN (SELECT a FROM t2 UNION SELECT a FROM t2)) dt JOIN tl ON dt.a= tl.a ORDER BY dt.a;
--let $exec_msg= ... and EXECUTE pstmt itself sends the derived table to the remote server
--let $log_pattern= select %
--source suite/federated/include/federatedx_pushdown_ps_exec_log.inc
SET global federated_pushdown=0;
CALL fill_tables();
EXECUTE pstmt;
DEALLOCATE PREPARE pstmt;
DROP TABLE tl;
--echo #
--echo # MDEV-41243, MDEV-41273: same assertion, but for a stored routine's own
--echo # pushed down UPDATE/DELETE, and for one it runs via dynamic SQL
--echo #
--echo # A pushed down UPDATE/DELETE that is one of a routine's own
--echo # instructions, CALLed with pushdown on and then off
CALL fill_tables();
delimiter |;
CREATE PROCEDURE p_upd_del()
BEGIN
UPDATE t1 AS x JOIN t2 AS y ON x.a= y.a SET x.b= y.c WHERE y.a= 1;
DELETE FROM x USING t1 AS x JOIN t2 AS y ON x.a= y.a WHERE y.a= 2;
END|
delimiter ;|
SET global federated_pushdown=1;
--echo # The routine's statements are sent to the remote server pushed down
--source suite/federated/include/federatedx_pushdown_log_start.inc
CALL p_upd_del();
--let $log_pattern= update %
--let $log_pattern2= delete %
--source suite/federated/include/federatedx_pushdown_log_show.inc
SET global federated_pushdown=0;
--echo # Reset the data so this second, non-pushed-down CALL actually
--echo # changes/deletes a row instead of silently matching none
CALL fill_tables();
--echo # Not pushed down: the remote server sees different statements
--source suite/federated/include/federatedx_pushdown_log_start.inc
CALL p_upd_del();
--let $log_pattern= update %
--let $log_pattern2= delete %
--source suite/federated/include/federatedx_pushdown_log_show.inc
--echo # rerun the same routine with pushdown enabled
SET global federated_pushdown=1;
--echo # The routine's statements are sent to the remote server pushed down
--source suite/federated/include/federatedx_pushdown_log_start.inc
CALL p_upd_del();
--let $log_pattern= update %
--let $log_pattern2= delete %
--source suite/federated/include/federatedx_pushdown_log_show.inc
SELECT * FROM t1 ORDER BY a;
--echo #
--echo # A pushed down statement in a routine that is CALLed from another
--echo # routine: the nested routine must not be frozen either
--echo #
CALL fill_tables();
delimiter |;
CREATE PROCEDURE p_outer()
BEGIN
CALL p_upd_del();
END|
delimiter ;|
SET global federated_pushdown=1;
--echo # Nested routine: statements still pushed down
--source suite/federated/include/federatedx_pushdown_log_start.inc
CALL p_outer();
--let $log_pattern= update %
--let $log_pattern2= delete %
--source suite/federated/include/federatedx_pushdown_log_show.inc
SET global federated_pushdown=0;
CALL fill_tables();
CALL p_outer();
SELECT * FROM t1 ORDER BY a;
--echo #
--echo # EXECUTE of a prepared statement inside a routine exercises the
--echo # interaction between sp_head::execute() and
--echo # Prepared_statement::execute_loop(), each of which decides on its own
--echo # whether to freeze its mem_root. The PREPARE stays
--echo # outside the routine so the SAME prepared statement, not a fresh one,
--echo # is EXECUTEd by both CALLs below
--echo #
CALL fill_tables();
PREPARE stmt FROM "UPDATE t1 AS x JOIN t2 AS y ON x.a= y.a SET x.b= y.c WHERE y.a= 1";
delimiter |;
CREATE PROCEDURE p_execute()
BEGIN
EXECUTE stmt;
END|
delimiter ;|
SET global federated_pushdown=1;
--echo # EXECUTE in routine: statement pushed down
--source suite/federated/include/federatedx_pushdown_log_start.inc
CALL p_execute();
--let $log_pattern= update %
--source suite/federated/include/federatedx_pushdown_log_show.inc
SET global federated_pushdown=0;
CALL fill_tables();
CALL p_execute();
DEALLOCATE PREPARE stmt;
SELECT * FROM t1 ORDER BY a;
--echo #
--echo # A static cursor's OPEN has no LEX of its own: it runs the cursor's
--echo # SELECT using the corresponding DECLARE (cpush) instruction's LEX
--echo # instead (see sp_instr_copen::execute())
--echo #
CALL fill_tables();
delimiter |;
CREATE PROCEDURE p_cursor()
BEGIN
DECLARE v_a INT;
DECLARE v_b VARCHAR(16);
DECLARE cur CURSOR FOR SELECT a, b FROM t1 WHERE a < 3;
OPEN cur;
FETCH cur INTO v_a, v_b;
CLOSE cur;
END|
delimiter ;|
SET global federated_pushdown=1;
--echo # Cursor SELECT pushed down
--source suite/federated/include/federatedx_pushdown_log_start.inc
CALL p_cursor();
--let $log_pattern= select %
--source suite/federated/include/federatedx_pushdown_log_show.inc
SET global federated_pushdown=0;
CALL fill_tables();
CALL p_cursor();
DROP PROCEDURE p_cursor;
--echo #
--echo # Pushdown off first (the statement is executed without pushdown),
--echo # then on, then off again, for a prepared statement and a routine
--echo #
SET global federated_pushdown=0;
CALL fill_tables();
--let $q= UPDATE t1 AS x JOIN t2 AS y ON x.a= y.a SET x.b= y.c WHERE y.a= 1
eval PREPARE pstmt FROM "$q";
CREATE PROCEDURE p_off_on()
UPDATE t1 AS x JOIN t2 AS y ON x.a= y.a SET x.b= y.c WHERE y.a= 1;
EXECUTE pstmt;
CALL p_off_on();
SET global federated_pushdown=1;
CALL fill_tables();
EXECUTE pstmt;
CALL p_off_on();
SET global federated_pushdown=0;
CALL fill_tables();
EXECUTE pstmt;
CALL p_off_on();
SELECT a, b FROM t1 ORDER BY a;
DEALLOCATE PREPARE pstmt;
DROP PROCEDURE p_off_on;
--echo #
--echo # A pushed down UPDATE/DELETE inside a stored function and inside a
--echo # trigger, executed with pushdown on, then off, then on again
--echo #
CALL fill_tables();
CREATE TABLE tl_trg (a int);
delimiter |;
CREATE FUNCTION f_upd_del() RETURNS int
BEGIN
UPDATE t1 AS x JOIN t2 AS y ON x.a= y.a SET x.b= y.c WHERE y.a= 1;
DELETE FROM x USING t1 AS x JOIN t2 AS y ON x.a= y.a WHERE y.a= 2;
RETURN 1;
END|
CREATE TRIGGER trg_upd_del AFTER INSERT ON tl_trg FOR EACH ROW
BEGIN
UPDATE t1 AS x JOIN t2 AS y ON x.a= y.a SET x.b= y.c WHERE y.a= 3;
DELETE FROM x USING t1 AS x JOIN t2 AS y ON x.a= y.a WHERE y.a= 3;
END|
delimiter ;|
SET global federated_pushdown=1;
--echo # Function: statements pushed down
--source suite/federated/include/federatedx_pushdown_log_start.inc
SELECT f_upd_del();
--let $log_pattern= update %
--let $log_pattern2= delete %
--source suite/federated/include/federatedx_pushdown_log_show.inc
--echo # Trigger: statements pushed down
--source suite/federated/include/federatedx_pushdown_log_start.inc
INSERT INTO tl_trg VALUES (1);
--let $log_pattern= update %
--let $log_pattern2= delete %
--source suite/federated/include/federatedx_pushdown_log_show.inc
SELECT * FROM t1 ORDER BY a;
SET global federated_pushdown=0;
CALL fill_tables();
SELECT f_upd_del();
INSERT INTO tl_trg VALUES (2);
SELECT * FROM t1 ORDER BY a;
SET global federated_pushdown=1;
CALL fill_tables();
SELECT f_upd_del();
INSERT INTO tl_trg VALUES (3);
SELECT * FROM t1 ORDER BY a;
DROP TRIGGER trg_upd_del;
DROP FUNCTION f_upd_del;
DROP TABLE tl_trg;
SET global federated_pushdown=0;
--echo #
--echo # A metadata-invalidation reparse of the routine's own instruction,
--echo # when the first post-reparse execution of that instruction happens
--echo # to be pushed down, must not hit the mem_root assertion
--echo #
CALL fill_tables();
delimiter |;
CREATE PROCEDURE p_reparse()
BEGIN
UPDATE t1 AS x JOIN t2 AS y ON x.a= y.a SET x.b= y.c WHERE y.a= 1;
END|
delimiter ;|
SET global federated_pushdown=1;
CALL p_reparse();
--echo # Force a metadata-invalidation reparse of p_reparse()'s instruction
--echo # on its next CALL. FederatedX doesn't support ALTER TABLE, so drop
--echo # and recreate t2 identically -- that still bumps its table version.
DROP TABLE t2;
--replace_result $SLAVE_MYPORT SLAVE_PORT
eval
CREATE TABLE t2 (a int, c varchar(16))
ENGINE="FEDERATED" DEFAULT CHARSET=latin1
CONNECTION='mysql://root@127.0.0.1:$SLAVE_MYPORT/federated/t2';
CALL fill_tables();
--echo # The first post-reparse execution runs with pushdown on
--source suite/federated/include/federatedx_pushdown_log_start.inc
CALL p_reparse();
--let $log_pattern= update %
--source suite/federated/include/federatedx_pushdown_log_show.inc
SET global federated_pushdown=0;
--echo # Reset the data so this non-pushed-down CALL actually changes a row
CALL fill_tables();
--echo # A later, non-pushed-down execution must work too
CALL p_reparse();
SELECT a, b FROM t1 ORDER BY a;
DROP PROCEDURE p_reparse;
DROP PROCEDURE p_upd_del;
DROP PROCEDURE p_outer;
DROP PROCEDURE p_execute;
DROP PROCEDURE fill_tables;
DROP TABLE federated.t1, federated.t2, federated.t3;

View File

@@ -0,0 +1,13 @@
# Stop capturing and show the captured statements matching
# $log_pattern, and $log_pattern2 if set (LIKE patterns).
connection slave;
SET global general_log=0;
--let $log_cond= argument LIKE '$log_pattern'
if ($log_pattern2)
{
--let $log_cond= $log_cond OR argument LIKE '$log_pattern2'
}
eval SELECT argument FROM mysql.general_log
WHERE $log_cond;
--let $log_pattern2=
connection master;

View File

@@ -0,0 +1,5 @@
# Start capturing the statements the remote server receives.
connection slave;
TRUNCATE TABLE mysql.general_log;
SET global general_log=1;
connection master;

View File

@@ -0,0 +1,11 @@
# Execute the prepared statement pstmt while capturing, on the remote
# server, the statements it receives.
# Input: $exec_msg (message to echo), $log_pattern (LIKE pattern for
# the statements to show)
--source suite/federated/include/federatedx_pushdown_log_start.inc
--echo # $exec_msg
--enable_info
EXECUTE pstmt;
--disable_info
--source suite/federated/include/federatedx_pushdown_log_show.inc

View File

@@ -20,6 +20,15 @@
#include "derived_handler.h"
derived_handler::derived_handler(THD *thd_arg, handlerton *ht_arg)
: thd(thd_arg), ht(ht_arg), derived(0), table(0), tmp_table_param(0),
unit(0), select(0)
{
/* Pushdown skips optimization, so don't freeze the mem_root */
thd_arg->lex->mark_pushed_down();
}
/**
The methods of the Pushdown_derived class.

View File

@@ -53,9 +53,7 @@ public:
SELECT_LEX *select; // The first select of the specification
derived_handler(THD *thd_arg, handlerton *ht_arg)
: thd(thd_arg), ht(ht_arg), derived(0),table(0), tmp_table_param(0),
unit(0), select(0) {}
derived_handler(THD *thd_arg, handlerton *ht_arg);
virtual ~derived_handler() = default;
/*

View File

@@ -193,7 +193,10 @@ pushdown_handler::pushdown_handler(THD *thd_arg, handlerton *ht_arg,
select_result *result_arg)
: select_lex(sel_lex), thd(thd_arg), ht(ht_arg), result(result_arg),
is_analyze(thd_arg->lex->analyze_stmt)
{}
{
/* Pushdown skips optimization, so don't freeze the mem_root */
thd_arg->lex->mark_pushed_down();
}
void pushdown_handler::print_error(int error, myf errflag)

View File

@@ -1570,13 +1570,14 @@ sp_head::execute(THD *thd, bool merge_da_on_success)
if (!err_status)
{
if (!(main_mem_root.flags & ROOT_FLAG_READ_ONLY) &&
has_all_instrs_executed())
!dont_freeze_mem_root && has_all_instrs_executed())
{
main_mem_root.flags |= ROOT_FLAG_READ_ONLY;
}
++executed_counter;
DBUG_PRINT("info", ("execute counter: %lu", executed_counter));
}
dont_freeze_mem_root= false;
#endif
m_flags&= ~IS_INVOKED;

View File

@@ -148,6 +148,14 @@ protected:
*/
ulong executed_counter;
#endif
#ifdef PROTECT_STATEMENT_MEMROOT
/*
Set when an executed instruction has LEX::dont_freeze_mem_root set.
Prevents freezing main_mem_root for this execution of the routine;
cleared at the end of sp_head::execute().
*/
bool dont_freeze_mem_root= false;
#endif
public:
/** Possible values of m_flags */
enum {
@@ -946,6 +954,7 @@ public:
#ifdef PROTECT_STATEMENT_MEMROOT
int has_all_instrs_executed();
void reset_instrs_executed_counter();
void set_dont_freeze_mem_root() { dont_freeze_mem_root= true; }
#endif
/* Add tables used by routine to the table list. */

View File

@@ -420,6 +420,15 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp,
res= instr->exec_core(thd, nextp);
DBUG_PRINT("info",("exec_core returned: %d", res));
}
#ifdef PROTECT_STATEMENT_MEMROOT
/* A pushed down instruction prevents freezing the routine's mem_root */
if (m_lex->dont_freeze_mem_root)
{
DBUG_ASSERT(thd->spcont && thd->spcont->m_sp);
DBUG_PRINT("info", ("mem_root protection of the routine is disabled"));
thd->spcont->m_sp->set_dont_freeze_mem_root();
}
#endif
/*
Call after unit->cleanup() to close open table
@@ -609,10 +618,11 @@ int sp_lex_keeper::validate_lex_and_exec_core(THD *thd, uint *nextp,
sp_lex_instr is re-parsed after the metadata change, which sets up a new
mem_root for reparsing. Once the sp_lex_instr is reparsed and re-executed
(via reset_lex_and_exec_core) it should be marked as read-only to enforce
sp memory root protection.
sp memory root protection, unless the statement was pushed down.
*/
#ifdef PROTECT_STATEMENT_MEMROOT
if (rerun_the_same_instr && instr->mem_root)
if (rerun_the_same_instr && instr->mem_root &&
!m_lex->dont_freeze_mem_root)
instr->mem_root->flags |= ROOT_FLAG_READ_ONLY;
#endif
break;

View File

@@ -3548,6 +3548,23 @@ public:
*/
bool autocommit; // Often used, better as bool
bool sp_lex_in_use; // Keep track on lex usage in SPs for error handling
/*
Set by the pushdown_handler and derived_handler constructors when the
statement is pushed down to an engine, which skips the once-per-statement
optimization. Such a statement must not freeze its mem_root.
Prepared_statement::execute_loop() clears it after each execution.
A stored routine instruction's LEX keeps it.
*/
#ifdef PROTECT_STATEMENT_MEMROOT
bool dont_freeze_mem_root= false;
#endif
/* To be called by the pushdown_handler and derived_handler constructors */
void mark_pushed_down()
{
#ifdef PROTECT_STATEMENT_MEMROOT
dont_freeze_mem_root= true;
#endif
}
/* Bit fields, reset for every query */
bool is_shutdown_wait_for_slaves:1;

View File

@@ -4891,7 +4891,10 @@ start_with_reprepare:
#ifdef PROTECT_STATEMENT_MEMROOT
if (!error)
{
mem_root->flags |= ROOT_FLAG_READ_ONLY;
if (!lex->dont_freeze_mem_root)
mem_root->flags |= ROOT_FLAG_READ_ONLY;
else
DBUG_PRINT("info", ("mem_root is not frozen: statement was pushed down"));
++executed_counter;
DBUG_PRINT("info", ("execute counter: %lu", executed_counter));
@@ -4902,6 +4905,7 @@ start_with_reprepare:
executed_counter= 0;
mem_root->flags &= ~ROOT_FLAG_READ_ONLY;
}
lex->dont_freeze_mem_root= false;
#endif
return error;