diff --git a/mysql-test/suite/federated/federatedx_create_handlers.result b/mysql-test/suite/federated/federatedx_create_handlers.result index 2f413f720de..52e538c3e4d 100644 --- a/mysql-test/suite/federated/federatedx_create_handlers.result +++ b/mysql-test/suite/federated/federatedx_create_handlers.result @@ -555,6 +555,491 @@ WHERE id=2) dt2) dt a b a b id name 1 1 NULL NULL NULL NULL 2 2 NULL NULL NULL NULL +# MDEV-25080: Allow pushdown of queries involving UNIONs +# in outer select to foreign engines +# +connection master; +TRUNCATE TABLE federated.t1; +TRUNCATE TABLE federated.t2; +INSERT INTO federated.t1 VALUES ('abc'), ('bcd'), ('cde'); +INSERT INTO federated.t2 VALUES ('abc'), ('bcd'), ('cde'), ('def'), ('efg'); +CREATE TABLE t3 (a varchar(30)) ENGINE=MyISAM; +CREATE TABLE t4 (a varchar(30)) ENGINE=MyISAM; +INSERT INTO t3 VALUES ('t3_myisam1'), ('t3_myisam2'), ('t3_myisam3'); +INSERT INTO t4 VALUES ('t4_myisam1'), ('t4_myisam2'), ('t4_myisam3'); +# Pushdown of the whole UNION +SELECT * from federated.t1 UNION SELECT * from federated.t2; +a +abc +bcd +cde +def +efg +EXPLAIN SELECT * from federated.t1 UNION SELECT * from federated.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 +# Pushdown of a part of the UNION +SELECT * from federated.t1 UNION SELECT * from t3; +a +abc +bcd +cde +t3_myisam1 +t3_myisam2 +t3_myisam3 +EXPLAIN SELECT * from federated.t1 UNION SELECT * from t3; +id select_type table type possible_keys key key_len ref rows Extra +1 PUSHED SELECT NULL NULL NULL NULL NULL NULL NULL NULL +2 UNION t3 ALL NULL NULL NULL NULL 3 +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +SELECT * from federated.t1 UNION ALL SELECT * from federated.t2; +a +abc +bcd +cde +abc +bcd +cde +def +efg +EXPLAIN SELECT * from federated.t1 UNION ALL SELECT * from federated.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 +EXPLAIN FORMAT=JSON SELECT * from federated.t1 UNION ALL +SELECT * from federated.t2; +EXPLAIN +{ + "query_block": { + "union_result": { + "message": "PUSHED UNION" + } + } +} +ANALYZE SELECT * from federated.t1 UNION ALL SELECT * from federated.t2; +id select_type table type possible_keys key key_len ref rows r_rows filtered r_filtered Extra +NULL PUSHED UNION NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL +ANALYZE FORMAT=JSON SELECT * from federated.t1 UNION ALL +SELECT * from federated.t2; +ANALYZE +{ + "query_optimization": { + "r_total_time_ms": "REPLACED" + }, + "query_block": { + "union_result": { + "message": "PUSHED UNION" + } + } +} +SELECT * from federated.t1 EXCEPT SELECT * from federated.t2; +a +EXPLAIN EXTENDED SELECT * from federated.t1 EXCEPT +SELECT * from federated.t2; +id select_type table type possible_keys key key_len ref rows filtered Extra +NULL PUSHED EXCEPT NULL NULL NULL NULL NULL NULL NULL NULL NULL +Warnings: +Note 1003 /* select#1 */ select `federated`.`t1`.`a` AS `a` from `federated`.`t1` except /* select#2 */ select `federated`.`t2`.`a` AS `a` from `federated`.`t2` +EXPLAIN FORMAT=JSON SELECT * from federated.t1 EXCEPT +SELECT * from federated.t2; +EXPLAIN +{ + "query_block": { + "union_result": { + "message": "PUSHED EXCEPT" + } + } +} +SELECT * from federated.t1 INTERSECT SELECT * from federated.t2; +a +abc +bcd +cde +EXPLAIN PARTITIONS SELECT * from federated.t1 INTERSECT +SELECT * from federated.t2; +id select_type table partitions type possible_keys key key_len ref rows Extra +NULL PUSHED INTERSECT NULL NULL NULL NULL NULL NULL NULL NULL NULL +EXPLAIN FORMAT=JSON SELECT * from federated.t1 INTERSECT +SELECT * from federated.t2; +EXPLAIN +{ + "query_block": { + "union_result": { + "message": "PUSHED INTERSECT" + } + } +} +# More than two SELECTs in a UNIT: +SELECT * from federated.t1 INTERSECT +SELECT * from federated.t2 UNION ALL +SELECT * from federated.t2 EXCEPT +SELECT * from federated.t1; +a +def +efg +EXPLAIN +SELECT count(*) from federated.t1 INTERSECT +SELECT count(*) from federated.t2 UNION ALL +SELECT count(*)+20 from federated.t2 EXCEPT +SELECT count(*)+5 from federated.t1; +id select_type table type possible_keys key key_len ref rows Extra +NULL PUSHED UNIT NULL NULL NULL NULL NULL NULL NULL NULL +EXPLAIN FORMAT=JSON +SELECT count(*) from federated.t1 INTERSECT +SELECT count(*) from federated.t2 UNION ALL +SELECT count(*)+20 from federated.t2 EXCEPT +SELECT count(*)+5 from federated.t1; +EXPLAIN +{ + "query_block": { + "union_result": { + "message": "PUSHED UNIT" + } + } +} +ANALYZE +SELECT count(*) from federated.t1 INTERSECT +SELECT count(*) from federated.t2 UNION +SELECT count(*)+20 from federated.t2 EXCEPT +SELECT count(*)+5 from federated.t1; +id select_type table type possible_keys key key_len ref rows r_rows filtered r_filtered Extra +NULL PUSHED UNIT NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL +# UNION inside a derived table: the whole derived table must be pushed +SELECT * FROM +(SELECT * FROM federated.t1 UNION ALL SELECT * FROM federated.t2) q; +a +abc +bcd +cde +abc +bcd +cde +def +efg +EXPLAIN +SELECT * FROM +(SELECT a FROM federated.t1 UNION ALL SELECT * FROM federated.t2) q; +id select_type table type possible_keys key key_len ref rows Extra +1 PUSHED SELECT NULL NULL NULL NULL NULL NULL NULL NULL +# There is an uncacheable side effect due to fetch into @var, +# so the UNION cannot be pushed down as a whole. +# But separate SELECTs can be pushed, and the results are combined +# at the server side +SELECT count(*) FROM federated.t1 UNION +SELECT count(*) FROM federated.t1 EXCEPT +SELECT count(*)+1 FROM federated.t1 +INTO @var; +EXPLAIN SELECT count(*) FROM federated.t1 UNION +SELECT count(*) FROM federated.t2 EXCEPT +SELECT count(*)+1 FROM federated.t1 +INTO @var; +id select_type table type possible_keys key key_len ref rows Extra +1 PUSHED SELECT NULL NULL NULL NULL NULL NULL NULL NULL +2 PUSHED SELECT NULL NULL NULL NULL NULL NULL NULL NULL +3 PUSHED SELECT NULL NULL NULL NULL NULL NULL NULL NULL +NULL UNIT RESULT ALL NULL NULL NULL NULL NULL +EXPLAIN FORMAT=JSON SELECT count(*) FROM federated.t1 UNION +SELECT count(*) FROM federated.t2 EXCEPT +SELECT count(*)+2 FROM federated.t2 +INTO @var; +EXPLAIN +{ + "query_block": { + "union_result": { + "table_name": "", + "access_type": "ALL", + "query_specifications": [ + { + "query_block": { + "select_id": 1, + "table": { + "message": "Pushed select" + } + } + }, + { + "query_block": { + "select_id": 2, + "operation": "UNION", + "table": { + "message": "Pushed select" + } + } + }, + { + "query_block": { + "select_id": 3, + "operation": "EXCEPT", + "table": { + "message": "Pushed select" + } + } + } + ] + } + } +} +# Prepared statements +PREPARE stmt FROM "SELECT * from federated.t1 INTERSECT + SELECT * from federated.t2 UNION ALL + SELECT * from federated.t2 EXCEPT + SELECT * from federated.t1"; +EXECUTE stmt; +a +def +efg +EXECUTE stmt; +a +def +efg +EXECUTE stmt; +a +def +efg +PREPARE stmt FROM "EXPLAIN SELECT * from federated.t1 INTERSECT + SELECT * from federated.t2 UNION ALL + SELECT * from federated.t2 EXCEPT + SELECT * from federated.t1"; +EXECUTE stmt; +id select_type table type possible_keys key key_len ref rows Extra +NULL PUSHED UNIT NULL NULL NULL NULL NULL NULL NULL NULL +EXECUTE stmt; +id select_type table type possible_keys key key_len ref rows Extra +NULL PUSHED UNIT NULL NULL NULL NULL NULL NULL NULL NULL +# UNIONs of mixed Federated/MyISAM tables, pushing parts of UNIONs +SELECT * FROM federated.t1 UNION SELECT * FROM t3; +a +abc +bcd +cde +t3_myisam1 +t3_myisam2 +t3_myisam3 +EXPLAIN SELECT * FROM federated.t1 UNION SELECT * FROM t3; +id select_type table type possible_keys key key_len ref rows Extra +1 PUSHED SELECT NULL NULL NULL NULL NULL NULL NULL NULL +2 UNION t3 ALL NULL NULL NULL NULL 3 +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +SELECT * FROM federated.t1 UNION ALL +SELECT * FROM t3 EXCEPT +SELECT * FROM federated.t2; +a +t3_myisam1 +t3_myisam2 +t3_myisam3 +EXPLAIN SELECT * FROM federated.t1 UNION ALL +SELECT * FROM t3 EXCEPT +SELECT * FROM federated.t2; +id select_type table type possible_keys key key_len ref rows Extra +1 PUSHED SELECT NULL NULL NULL NULL NULL NULL NULL NULL +2 UNION t3 ALL NULL NULL NULL NULL 3 +3 PUSHED SELECT NULL NULL NULL NULL NULL NULL NULL NULL +NULL UNIT RESULT ALL NULL NULL NULL NULL NULL +SELECT * FROM t3 UNION ALL +SELECT * FROM federated.t1 EXCEPT +SELECT * FROM t4 INTERSECT +SELECT * FROM federated.t2; +a +t3_myisam1 +t3_myisam2 +t3_myisam3 +abc +bcd +cde +EXPLAIN SELECT * FROM t3 UNION ALL +SELECT * FROM federated.t1 EXCEPT +SELECT * FROM t4 INTERSECT +SELECT * FROM federated.t2; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t3 ALL NULL NULL NULL NULL 3 +2 PUSHED SELECT NULL NULL NULL NULL NULL NULL NULL NULL +5 EXCEPT ALL NULL NULL NULL NULL 3 +3 DERIVED t4 ALL NULL NULL NULL NULL 3 +4 INTERSECT t2 ALL NULL NULL NULL NULL 5 +NULL INTERSECT RESULT ALL NULL NULL NULL NULL NULL +NULL UNIT RESULT ALL NULL NULL NULL NULL NULL +SELECT * FROM federated.t2 UNION ALL +SELECT * FROM t3 EXCEPT +SELECT * FROM t4 INTERSECT +SELECT * FROM federated.t1; +a +abc +bcd +cde +def +efg +t3_myisam1 +t3_myisam2 +t3_myisam3 +EXPLAIN SELECT * FROM federated.t2 UNION ALL +SELECT * FROM t3 EXCEPT +SELECT * FROM t4 INTERSECT +SELECT * FROM federated.t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PUSHED SELECT NULL NULL NULL NULL NULL NULL NULL NULL +2 UNION t3 ALL NULL NULL NULL NULL 3 +5 EXCEPT ALL NULL NULL NULL NULL 3 +3 DERIVED t4 ALL NULL NULL NULL NULL 3 +4 INTERSECT t1 ALL NULL NULL NULL NULL 3 +NULL INTERSECT RESULT ALL NULL NULL NULL NULL NULL +NULL UNIT RESULT ALL NULL NULL NULL NULL NULL +# Parenthesis must not prevent the whole UNIONs pushdown +EXPLAIN (SELECT * FROM federated.t1 UNION +SELECT * FROM federated.t2) UNION ALL +SELECT * FROM federated.t1; +id select_type table type possible_keys key key_len ref rows Extra +NULL PUSHED UNION NULL NULL NULL NULL NULL NULL NULL NULL +(SELECT * FROM federated.t1 UNION +SELECT * FROM federated.t2) UNION ALL +SELECT * FROM federated.t1; +a +abc +bcd +cde +def +efg +abc +bcd +cde +EXPLAIN (SELECT * FROM federated.t1 UNION SELECT * FROM federated.t2) +UNION ALL (SELECT * FROM federated.t1 UNION SELECT * FROM federated.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 +(SELECT * FROM federated.t1 UNION SELECT * FROM federated.t2) UNION ALL +(SELECT * FROM federated.t1 UNION SELECT * FROM federated.t2); +a +abc +bcd +cde +def +efg +abc +bcd +cde +def +efg +# Union of tables containing different INT data types +connection slave; +CREATE TABLE federated.t11 (a smallint(6) NOT NULL); +INSERT INTO federated.t11 VALUES (-32678), (-1), (0); +CREATE TABLE federated.t12 (a int(10) UNSIGNED NOT NULL); +INSERT INTO federated.t12 VALUES (0), (1), (32767); +connection master; +CREATE TABLE federated.t11 (a smallint(6) NOT NULL) +ENGINE="FEDERATED" +CONNECTION='mysql://root@127.0.0.1:SLAVE_PORT/federated/t11'; +CREATE TABLE federated.t12 (a int(10) UNSIGNED NOT NULL) +ENGINE="FEDERATED" +CONNECTION='mysql://root@127.0.0.1:SLAVE_PORT/federated/t12'; +# Entire UNION pushdown +SELECT a FROM federated.t12 UNION ALL SELECT a FROM federated.t11; +a +0 +1 +32767 +-32678 +-1 +0 +EXPLAIN SELECT a FROM federated.t12 UNION ALL SELECT a FROM federated.t11; +id select_type table type possible_keys key key_len ref rows Extra +NULL PUSHED UNION NULL NULL NULL NULL NULL NULL NULL NULL +SELECT a FROM federated.t11 UNION SELECT a FROM federated.t12; +a +-32678 +-1 +0 +1 +32767 +# Partial pushdown of SELECTs composing the UNION +SELECT a FROM federated.t12 UNION SELECT a FROM federated.t11 UNION SELECT 123; +a +0 +1 +32767 +-32678 +-1 +123 +EXPLAIN +SELECT a FROM federated.t12 UNION SELECT a FROM federated.t11 +UNION SELECT 123; +id select_type table type possible_keys key key_len ref rows Extra +1 PUSHED SELECT NULL NULL NULL NULL NULL NULL NULL NULL +2 PUSHED SELECT NULL NULL NULL NULL NULL NULL NULL NULL +3 UNION NULL NULL NULL NULL NULL NULL NULL No tables used +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +SELECT a FROM federated.t12 EXCEPT +SELECT 1 UNION ALL +SELECT a FROM federated.t11 EXCEPT +SELECT 0; +a +-32678 +32767 +-1 +# Union of tables containing different string data types +connection slave; +CREATE TABLE federated.t13 (a CHAR(6)); +INSERT INTO federated.t13 VALUES ('t13abc'), ('t13xx'), ('common'); +CREATE TABLE federated.t14 (a VARCHAR(8)); +INSERT INTO federated.t14 VALUES ('t14abcde'), ('t14xyzzz'), ('common'); +connection master; +CREATE TABLE federated.t13 (a CHAR(6)) +ENGINE="FEDERATED" +CONNECTION='mysql://root@127.0.0.1:SLAVE_PORT/federated/t13'; +CREATE TABLE federated.t14 (a VARCHAR(8)) +ENGINE="FEDERATED" +CONNECTION='mysql://root@127.0.0.1:SLAVE_PORT/federated/t14'; +SELECT * FROM federated.t13 UNION SELECT * FROM federated.t14; +a +t13abc +t13xx +common +t14abcde +t14xyzzz +EXPLAIN SELECT * FROM federated.t13 UNION SELECT * FROM federated.t14; +id select_type table type possible_keys key key_len ref rows Extra +NULL PUSHED UNION NULL NULL NULL NULL NULL NULL NULL NULL +SELECT * FROM federated.t14 UNION ALL SELECT * FROM federated.t13; +a +t14abcde +t14xyzzz +common +t13abc +t13xx +common +SELECT * FROM federated.t14 UNION +SELECT * FROM federated.t13 UNION +SELECT '123456789000'; +a +t14abcde +t14xyzzz +common +t13abc +t13xx +123456789000 +EXPLAIN SELECT * FROM federated.t14 UNION +SELECT * FROM federated.t13 UNION +SELECT '123456789000'; +id select_type table type possible_keys key key_len ref rows Extra +1 PUSHED SELECT NULL NULL NULL NULL NULL NULL NULL NULL +2 PUSHED SELECT NULL NULL NULL NULL NULL NULL NULL NULL +3 UNION NULL NULL NULL NULL NULL NULL NULL No tables used +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +SELECT * FROM federated.t13 UNION +SELECT '123456789000' UNION +SELECT * FROM federated.t14; +a +t13abc +t13xx +common +123456789000 +t14abcde +t14xyzzz +connection master; +DROP TABLES federated.t1, federated.t2, t3, t4, federated.t11, federated.t12, +federated.t13, federated.t14; +connection slave; +DROP TABLES federated.t1, federated.t2, federated.t11, federated.t12, +federated.t13, federated.t14; +connection default; set global federated_pushdown=0; connection master; DROP TABLE IF EXISTS federated.t1; diff --git a/mysql-test/suite/federated/federatedx_create_handlers.test b/mysql-test/suite/federated/federatedx_create_handlers.test index 566aee6a0d6..ea9e5bbd105 100644 --- a/mysql-test/suite/federated/federatedx_create_handlers.test +++ b/mysql-test/suite/federated/federatedx_create_handlers.test @@ -375,6 +375,267 @@ SELECT * FROM t10 LEFT JOIN WHERE id=2) dt2) dt ) ON t10.a=t11.a; +--echo # MDEV-25080: Allow pushdown of queries involving UNIONs +--echo # in outer select to foreign engines +--echo # + +connection master; + +TRUNCATE TABLE federated.t1; +TRUNCATE TABLE federated.t2; + +INSERT INTO federated.t1 VALUES ('abc'), ('bcd'), ('cde'); +INSERT INTO federated.t2 VALUES ('abc'), ('bcd'), ('cde'), ('def'), ('efg'); + +CREATE TABLE t3 (a varchar(30)) ENGINE=MyISAM; +CREATE TABLE t4 (a varchar(30)) ENGINE=MyISAM; + +INSERT INTO t3 VALUES ('t3_myisam1'), ('t3_myisam2'), ('t3_myisam3'); +INSERT INTO t4 VALUES ('t4_myisam1'), ('t4_myisam2'), ('t4_myisam3'); + +--echo # Pushdown of the whole UNION +SELECT * from federated.t1 UNION SELECT * from federated.t2; +EXPLAIN SELECT * from federated.t1 UNION SELECT * from federated.t2; + +--echo # Pushdown of a part of the UNION +SELECT * from federated.t1 UNION SELECT * from t3; +EXPLAIN SELECT * from federated.t1 UNION SELECT * from t3; + +SELECT * from federated.t1 UNION ALL SELECT * from federated.t2; +EXPLAIN SELECT * from federated.t1 UNION ALL SELECT * from federated.t2; + +EXPLAIN FORMAT=JSON SELECT * from federated.t1 UNION ALL + SELECT * from federated.t2; + +ANALYZE SELECT * from federated.t1 UNION ALL SELECT * from federated.t2; + +--source include/analyze-format.inc +ANALYZE FORMAT=JSON SELECT * from federated.t1 UNION ALL + SELECT * from federated.t2; + +SELECT * from federated.t1 EXCEPT SELECT * from federated.t2; + +EXPLAIN EXTENDED SELECT * from federated.t1 EXCEPT + SELECT * from federated.t2; + +EXPLAIN FORMAT=JSON SELECT * from federated.t1 EXCEPT + SELECT * from federated.t2; + +SELECT * from federated.t1 INTERSECT SELECT * from federated.t2; + +EXPLAIN PARTITIONS SELECT * from federated.t1 INTERSECT + SELECT * from federated.t2; + +EXPLAIN FORMAT=JSON SELECT * from federated.t1 INTERSECT + SELECT * from federated.t2; + +--echo # More than two SELECTs in a UNIT: +SELECT * from federated.t1 INTERSECT + SELECT * from federated.t2 UNION ALL + SELECT * from federated.t2 EXCEPT + SELECT * from federated.t1; + +EXPLAIN + SELECT count(*) from federated.t1 INTERSECT + SELECT count(*) from federated.t2 UNION ALL + SELECT count(*)+20 from federated.t2 EXCEPT + SELECT count(*)+5 from federated.t1; + +EXPLAIN FORMAT=JSON + SELECT count(*) from federated.t1 INTERSECT + SELECT count(*) from federated.t2 UNION ALL + SELECT count(*)+20 from federated.t2 EXCEPT + SELECT count(*)+5 from federated.t1; + +ANALYZE + SELECT count(*) from federated.t1 INTERSECT + SELECT count(*) from federated.t2 UNION + SELECT count(*)+20 from federated.t2 EXCEPT + SELECT count(*)+5 from federated.t1; + +--echo # UNION inside a derived table: the whole derived table must be pushed +SELECT * FROM + (SELECT * FROM federated.t1 UNION ALL SELECT * FROM federated.t2) q; + +EXPLAIN + SELECT * FROM + (SELECT a FROM federated.t1 UNION ALL SELECT * FROM federated.t2) q; + +--echo # There is an uncacheable side effect due to fetch into @var, +--echo # so the UNION cannot be pushed down as a whole. +--echo # But separate SELECTs can be pushed, and the results are combined +--echo # at the server side + +--disable_warnings + +SELECT count(*) FROM federated.t1 UNION + SELECT count(*) FROM federated.t1 EXCEPT + SELECT count(*)+1 FROM federated.t1 + INTO @var; + +EXPLAIN SELECT count(*) FROM federated.t1 UNION + SELECT count(*) FROM federated.t2 EXCEPT + SELECT count(*)+1 FROM federated.t1 + INTO @var; + +EXPLAIN FORMAT=JSON SELECT count(*) FROM federated.t1 UNION + SELECT count(*) FROM federated.t2 EXCEPT + SELECT count(*)+2 FROM federated.t2 + INTO @var; + +--enable_warnings + +--echo # Prepared statements +PREPARE stmt FROM "SELECT * from federated.t1 INTERSECT + SELECT * from federated.t2 UNION ALL + SELECT * from federated.t2 EXCEPT + SELECT * from federated.t1"; + +EXECUTE stmt; +EXECUTE stmt; +EXECUTE stmt; + +PREPARE stmt FROM "EXPLAIN SELECT * from federated.t1 INTERSECT + SELECT * from federated.t2 UNION ALL + SELECT * from federated.t2 EXCEPT + SELECT * from federated.t1"; + +EXECUTE stmt; +EXECUTE stmt; + +--echo # UNIONs of mixed Federated/MyISAM tables, pushing parts of UNIONs +SELECT * FROM federated.t1 UNION SELECT * FROM t3; +EXPLAIN SELECT * FROM federated.t1 UNION SELECT * FROM t3; + +SELECT * FROM federated.t1 UNION ALL + SELECT * FROM t3 EXCEPT + SELECT * FROM federated.t2; +EXPLAIN SELECT * FROM federated.t1 UNION ALL + SELECT * FROM t3 EXCEPT + SELECT * FROM federated.t2; + +SELECT * FROM t3 UNION ALL + SELECT * FROM federated.t1 EXCEPT + SELECT * FROM t4 INTERSECT + SELECT * FROM federated.t2; +EXPLAIN SELECT * FROM t3 UNION ALL + SELECT * FROM federated.t1 EXCEPT + SELECT * FROM t4 INTERSECT + SELECT * FROM federated.t2; + +SELECT * FROM federated.t2 UNION ALL + SELECT * FROM t3 EXCEPT + SELECT * FROM t4 INTERSECT + SELECT * FROM federated.t1; +EXPLAIN SELECT * FROM federated.t2 UNION ALL + SELECT * FROM t3 EXCEPT + SELECT * FROM t4 INTERSECT + SELECT * FROM federated.t1; + +--echo # Parenthesis must not prevent the whole UNIONs pushdown +EXPLAIN (SELECT * FROM federated.t1 UNION + SELECT * FROM federated.t2) UNION ALL + SELECT * FROM federated.t1; + +(SELECT * FROM federated.t1 UNION + SELECT * FROM federated.t2) UNION ALL + SELECT * FROM federated.t1; + +EXPLAIN (SELECT * FROM federated.t1 UNION SELECT * FROM federated.t2) + UNION ALL (SELECT * FROM federated.t1 UNION SELECT * FROM federated.t2); + +(SELECT * FROM federated.t1 UNION SELECT * FROM federated.t2) UNION ALL + (SELECT * FROM federated.t1 UNION SELECT * FROM federated.t2); + +--echo # Union of tables containing different INT data types +connection slave; +CREATE TABLE federated.t11 (a smallint(6) NOT NULL); +INSERT INTO federated.t11 VALUES (-32678), (-1), (0); + +CREATE TABLE federated.t12 (a int(10) UNSIGNED NOT NULL); +INSERT INTO federated.t12 VALUES (0), (1), (32767); + +connection master; + +--replace_result $SLAVE_MYPORT SLAVE_PORT +eval +CREATE TABLE federated.t11 (a smallint(6) NOT NULL) +ENGINE="FEDERATED" +CONNECTION='mysql://root@127.0.0.1:$SLAVE_MYPORT/federated/t11'; + +--replace_result $SLAVE_MYPORT SLAVE_PORT +eval +CREATE TABLE federated.t12 (a int(10) UNSIGNED NOT NULL) +ENGINE="FEDERATED" +CONNECTION='mysql://root@127.0.0.1:$SLAVE_MYPORT/federated/t12'; + +--echo # Entire UNION pushdown +SELECT a FROM federated.t12 UNION ALL SELECT a FROM federated.t11; +EXPLAIN SELECT a FROM federated.t12 UNION ALL SELECT a FROM federated.t11; + +SELECT a FROM federated.t11 UNION SELECT a FROM federated.t12; + +--echo # Partial pushdown of SELECTs composing the UNION +SELECT a FROM federated.t12 UNION SELECT a FROM federated.t11 UNION SELECT 123; +EXPLAIN + SELECT a FROM federated.t12 UNION SELECT a FROM federated.t11 + UNION SELECT 123; + +SELECT a FROM federated.t12 EXCEPT + SELECT 1 UNION ALL + SELECT a FROM federated.t11 EXCEPT + SELECT 0; + +--echo # Union of tables containing different string data types +connection slave; +CREATE TABLE federated.t13 (a CHAR(6)); +INSERT INTO federated.t13 VALUES ('t13abc'), ('t13xx'), ('common'); + +CREATE TABLE federated.t14 (a VARCHAR(8)); +INSERT INTO federated.t14 VALUES ('t14abcde'), ('t14xyzzz'), ('common'); + +connection master; + +--replace_result $SLAVE_MYPORT SLAVE_PORT +eval +CREATE TABLE federated.t13 (a CHAR(6)) +ENGINE="FEDERATED" +CONNECTION='mysql://root@127.0.0.1:$SLAVE_MYPORT/federated/t13'; + +--replace_result $SLAVE_MYPORT SLAVE_PORT +eval +CREATE TABLE federated.t14 (a VARCHAR(8)) +ENGINE="FEDERATED" +CONNECTION='mysql://root@127.0.0.1:$SLAVE_MYPORT/federated/t14'; + +SELECT * FROM federated.t13 UNION SELECT * FROM federated.t14; +EXPLAIN SELECT * FROM federated.t13 UNION SELECT * FROM federated.t14; + +SELECT * FROM federated.t14 UNION ALL SELECT * FROM federated.t13; + +SELECT * FROM federated.t14 UNION + SELECT * FROM federated.t13 UNION + SELECT '123456789000'; + +EXPLAIN SELECT * FROM federated.t14 UNION + SELECT * FROM federated.t13 UNION + SELECT '123456789000'; + +SELECT * FROM federated.t13 UNION + SELECT '123456789000' UNION + SELECT * FROM federated.t14; + +# Cleanup +connection master; +DROP TABLES federated.t1, federated.t2, t3, t4, federated.t11, federated.t12, + federated.t13, federated.t14; + +connection slave; +DROP TABLES federated.t1, federated.t2, federated.t11, federated.t12, + federated.t13, federated.t14; + +connection default; + set global federated_pushdown=0; source include/federated_cleanup.inc; diff --git a/sql/handler.h b/sql/handler.h index 464c4a32651..8c96d7a61d8 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -56,6 +56,7 @@ class Field_string; class Field_varstring; class Field_blob; class Column_definition; +class select_result; // the following is for checking tables @@ -1206,6 +1207,7 @@ class derived_handler; class select_handler; struct Query; typedef class st_select_lex SELECT_LEX; +typedef class st_select_lex_unit SELECT_LEX_UNIT; typedef struct st_order ORDER; /* @@ -1551,10 +1553,18 @@ struct handlerton derived_handler *(*create_derived)(THD *thd, TABLE_LIST *derived); /* - Create and return a select_handler if the storage engine can execute - the select statement 'select, otherwise return NULL + Create and return a select_handler for a single SELECT. + If the storage engine cannot execute the select statement, return NULL */ - select_handler *(*create_select) (THD *thd, SELECT_LEX *select); + select_handler *(*create_select) (THD *thd, SELECT_LEX *select_lex, + SELECT_LEX_UNIT *select_lex_unit); + + /* + Create and return a select_handler for a unit (i.e. multiple SELECTs + combined with UNION/EXCEPT/INTERSECT). If the storage engine cannot execute + the statement, return NULL + */ + select_handler *(*create_unit)(THD *thd, SELECT_LEX_UNIT *select_unit); /********************************************************************* Table discovery API. diff --git a/sql/mysqld.h b/sql/mysqld.h index 43194dec639..658aef3a259 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -922,7 +922,10 @@ enum enum_query_type // The temporary tables used by the query might be freed by the time // this print() call is made. - QT_DONT_ACCESS_TMP_TABLES= (1 << 13) + QT_DONT_ACCESS_TMP_TABLES= (1 << 13), + + // Print only the SELECT part, even for INSERT...SELECT + QT_SELECT_ONLY = (1 << 14) }; diff --git a/sql/select_handler.cc b/sql/select_handler.cc index b0b8e58623d..e80e2548d5b 100644 --- a/sql/select_handler.cc +++ b/sql/select_handler.cc @@ -17,6 +17,7 @@ #include "mariadb.h" #include "sql_priv.h" #include "sql_select.h" +#include "sql_cte.h" #include "select_handler.h" @@ -36,11 +37,26 @@ */ -select_handler::select_handler(THD *thd_arg, handlerton *ht_arg) - : thd(thd_arg), ht(ht_arg), table(NULL), - is_analyze(thd_arg->lex->analyze_stmt) +select_handler::select_handler(THD *thd_arg, handlerton *ht_arg, + SELECT_LEX *sel_lex) + : select_lex(sel_lex), lex_unit(nullptr), table(nullptr), + thd(thd_arg), ht(ht_arg), result(sel_lex->join->result), + is_analyze(thd_arg->lex->analyze_stmt) {} +select_handler::select_handler(THD *thd_arg, handlerton *ht_arg, + SELECT_LEX_UNIT *sel_unit) + : select_lex(nullptr), lex_unit(sel_unit), table(nullptr), + thd(thd_arg), ht(ht_arg), result(sel_unit->result), + is_analyze(thd_arg->lex->analyze_stmt) +{} + +select_handler::select_handler(THD *thd_arg, handlerton *ht_arg, + SELECT_LEX *sel_lex, SELECT_LEX_UNIT *sel_unit) + : select_lex(sel_lex), lex_unit(sel_unit), table(nullptr), thd(thd_arg), + ht(ht_arg), result(sel_lex->join->result), + is_analyze(thd_arg->lex->analyze_stmt) +{} select_handler::~select_handler() { @@ -49,21 +65,40 @@ select_handler::~select_handler() } -TABLE *select_handler::create_tmp_table(THD *thd, SELECT_LEX *select) +TABLE *select_handler::create_tmp_table(THD *thd) { List types; TMP_TABLE_PARAM tmp_table_param; - TABLE *table; DBUG_ENTER("select_handler::create_tmp_table"); - if (select->master_unit()->join_union_item_types(thd, types, 1)) + SELECT_LEX_UNIT *unit= nullptr; + uint unit_parts_count= 0; + + if (lex_unit) + { + unit= lex_unit; + SELECT_LEX *sl= unit->first_select(); + while (sl) + { + unit_parts_count++; + sl= sl->next_select(); + } + } + else + { + unit= select_lex->master_unit(); + unit_parts_count= 1; + } + + if (unit->join_union_item_types(thd, types, unit_parts_count)) DBUG_RETURN(NULL); + tmp_table_param.init(); tmp_table_param.field_count= tmp_table_param.func_count= types.elements; - table= ::create_tmp_table(thd, &tmp_table_param, types, - (ORDER *) 0, false, 0, - TMP_TABLE_ALL_COLUMNS, 1, - &empty_clex_str, true, false); + TABLE *table= ::create_tmp_table(thd, &tmp_table_param, types, + (ORDER *) 0, false, 0, + TMP_TABLE_ALL_COLUMNS, 1, + &empty_clex_str, true, false); DBUG_RETURN(table); } @@ -75,7 +110,7 @@ bool select_handler::prepare() Some engines (e.g. XPand) initialize "table" on their own. So we need to create a temporary table only if "table" is NULL. */ - if (!table && !(table= create_tmp_table(thd, select))) + if (!table && !(table= create_tmp_table(thd))) DBUG_RETURN(true); DBUG_RETURN(table->fill_item_list(&result_columns)); } @@ -92,22 +127,19 @@ bool select_handler::send_result_set_metadata() DBUG_RETURN(false); } #endif /* WITH_WSREP */ - if (select->join->result->send_result_set_metadata(result_columns, - Protocol::SEND_NUM_ROWS | - Protocol::SEND_EOF)) - DBUG_RETURN(true); - - DBUG_RETURN(false); + + DBUG_RETURN(result->send_result_set_metadata( + result_columns, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)); } bool select_handler::send_data() { - DBUG_ENTER("Pushdown_select::send_data"); - - if (select->join->result->send_data(result_columns)) + DBUG_ENTER("select_handler::send_data"); + int res= result->send_data(result_columns); + // "-1" means "duplicate when executing UNION" + if (res && res != -1) DBUG_RETURN(true); - DBUG_RETURN(false); } @@ -115,10 +147,7 @@ bool select_handler::send_data() bool select_handler::send_eof() { DBUG_ENTER("select_handler::send_eof"); - - if (select->join->result->send_eof()) - DBUG_RETURN(true); - DBUG_RETURN(false); + DBUG_RETURN(result->send_eof()); } diff --git a/sql/select_handler.h b/sql/select_handler.h index 5cc63231641..ea59eab4250 100644 --- a/sql/select_handler.h +++ b/sql/select_handler.h @@ -30,25 +30,18 @@ class select_handler { public: - THD *thd; - handlerton *ht; + // Constructor for a single SELECT_LEX (not a part of a unit) + select_handler(THD *thd_arg, handlerton *ht_arg, SELECT_LEX *sel_lex); - SELECT_LEX *select; // Select to be excuted + // Constructor for a unit (UNION/EXCEPT/INTERSECT) + select_handler(THD *thd_arg, handlerton *ht_arg, SELECT_LEX_UNIT *sel_unit); /* - Temporary table where all results should be stored in record[0] - The table has a field for every item from the select_lex::item_list. - The table is actually never filled. Only its record buffer is used. + Constructor for a SELECT_LEX which is a part of a unit + (partial pushdown). Both SELECT_LEX and SELECT_LEX_UNIT are passed */ - TABLE *table; - List result_columns; - - bool is_analyze; - - bool send_result_set_metadata(); - bool send_data(); - - select_handler(THD *thd_arg, handlerton *ht_arg); + select_handler(THD *thd_arg, handlerton *ht_arg, SELECT_LEX *sel_lex, + SELECT_LEX_UNIT *sel_unit); virtual ~select_handler(); @@ -56,9 +49,29 @@ class select_handler virtual bool prepare(); - static TABLE *create_tmp_table(THD *thd, SELECT_LEX *sel); + /* + Select_handler processes one of + - single SELECT + - whole unit (multiple SELECTs combined with UNION/EXCEPT/INTERSECT) + - single SELECT that is part of a unit (partial pushdown) + + In the case of single SELECT select_lex is initialized and lex_unit==NULL, + in the case of whole UNIT select_lex == NULL and lex_unit is initialized, + in the case of partial pushdown both select_lex and lex_unit + are initialized + */ + SELECT_LEX *select_lex; // Single select to be executed + SELECT_LEX_UNIT *lex_unit; // Unit to be executed + + /* + Temporary table where all results should be stored in record[0] + The table has a field for every item from the select_lex::item_list. + The table is actually never filled. Only its record buffer is used. + */ + TABLE *table; protected: + /* Functions to scan the select result set. All these returns 0 if ok, error code in case of error. @@ -80,7 +93,19 @@ protected: /* Report errors */ virtual void print_error(int error, myf errflag); + bool send_result_set_metadata(); + bool send_data(); bool send_eof(); + + TABLE *create_tmp_table(THD *thd); + + THD *thd; + handlerton *ht; + + select_result *result; // Object receiving the retrieved data + List result_columns; + + bool is_analyze; }; #endif /* SELECT_HANDLER_INCLUDED */ diff --git a/sql/sql_derived.cc b/sql/sql_derived.cc index 4ceebcc1978..78c38d7f17a 100644 --- a/sql/sql_derived.cc +++ b/sql/sql_derived.cc @@ -894,22 +894,6 @@ bool mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *derived) first_select->mark_as_belong_to_derived(derived); derived->dt_handler= derived->find_derived_handler(thd); - if (derived->dt_handler) - { - char query_buff[4096]; - String derived_query(query_buff, sizeof(query_buff), thd->charset()); - derived_query.length(0); - derived->derived->print(&derived_query, - enum_query_type(QT_VIEW_INTERNAL | - QT_ITEM_ORIGINAL_FUNC_NULLIF | - QT_PARSABLE)); - if (!thd->make_lex_string(&derived->derived_spec, - derived_query.ptr(), derived_query.length())) - { - delete derived->dt_handler; - derived->dt_handler= NULL; - } - } exit: /* Hide "Unknown column" or "Unknown function" error */ diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc index 639a45bf848..922723f2947 100644 --- a/sql/sql_explain.cc +++ b/sql/sql_explain.cc @@ -36,6 +36,11 @@ const char *unit_operation_text[4]= "UNIT RESULT","UNION RESULT","INTERSECT RESULT","EXCEPT RESULT" }; +const char *pushed_unit_operation_text[4]= +{ + "PUSHED UNIT", "PUSHED UNION", "PUSHED INTERSECT", "PUSHED EXCEPT" +}; + const char *pushed_derived_text= "PUSHED DERIVED"; const char *pushed_select_text= "PUSHED SELECT"; @@ -592,7 +597,22 @@ uint Explain_union::make_union_table_name(char *buf) } -int Explain_union::print_explain(Explain_query *query, +int Explain_union::print_explain(Explain_query *query, + select_result_sink *output, + uint8 explain_flags, bool is_analyze) +{ + if (is_pushed_down_to_engine) + return print_explain_pushed_down(output, explain_flags, is_analyze); + else + return print_explain_regular(query, output, explain_flags, is_analyze); +} + +/* + Prints EXPLAIN plan for a regular UNIT (UNION/EXCEPT/INTERSECT), + i.e. UNIT that has not been pushed down to a storage engine +*/ + +int Explain_union::print_explain_regular(Explain_query *query, select_result_sink *output, uint8 explain_flags, bool is_analyze) @@ -609,7 +629,15 @@ int Explain_union::print_explain(Explain_query *query, } if (!using_tmp) + { + /* + The union operation may not employ a temporary table, for example, + for UNION ALL, in that case the results of the query are sent directly + to the output. So there is no actual UNION operation and we don't need + to print the line in the EXPLAIN output. + */ return 0; + } /* Print a line with "UNIT RESULT" */ List item_list; @@ -626,7 +654,7 @@ int Explain_union::print_explain(Explain_query *query, item_list.push_back(new (mem_root) Item_string_sys(thd, table_name_buffer, len), mem_root); - + /* `partitions` column */ if (explain_flags & DESCRIBE_PARTITIONS) item_list.push_back(item_null, mem_root); @@ -682,7 +710,6 @@ int Explain_union::print_explain(Explain_query *query, extra_buf.length()), mem_root); - //output->unit.offset_limit_cnt= 0; if (output->send_data(item_list)) return 1; @@ -694,9 +721,89 @@ int Explain_union::print_explain(Explain_query *query, } -void Explain_union::print_explain_json(Explain_query *query, +/* + Prints EXPLAIN plan for a UNIT (UNION/EXCEPT/INTERSECT) that + has been pushed down to a storage engine +*/ + +int Explain_union::print_explain_pushed_down(select_result_sink *output, + uint8 explain_flags, + bool is_analyze) +{ + THD *thd= output->thd; + MEM_ROOT *mem_root= thd->mem_root; + List item_list; + Item *item_null= new (mem_root) Item_null(thd); + + /* `id` column */ + item_list.push_back(item_null, mem_root); + + /* `select_type` column */ + push_str(thd, &item_list, fake_select_type); + + /* `table` column */ + item_list.push_back(item_null, mem_root); + + /* `partitions` column */ + if (explain_flags & DESCRIBE_PARTITIONS) + item_list.push_back(item_null, mem_root); + + /* `type` column */ + item_list.push_back(item_null, mem_root); + + /* `possible_keys` column */ + item_list.push_back(item_null, mem_root); + + /* `key` */ + item_list.push_back(item_null, mem_root); + + /* `key_len` */ + item_list.push_back(item_null, mem_root); + + /* `ref` */ + item_list.push_back(item_null, mem_root); + + /* `rows` */ + item_list.push_back(item_null, mem_root); + + /* `r_rows` */ + if (is_analyze) + item_list.push_back(item_null, mem_root); + + /* `filtered` */ + if (explain_flags & DESCRIBE_EXTENDED || is_analyze) + item_list.push_back(item_null, mem_root); + + /* `r_filtered` */ + if (is_analyze) + item_list.push_back(item_null, mem_root); + + /* `Extra` */ + item_list.push_back(item_null, mem_root); + + if (output->send_data(item_list)) + return 1; + return 0; +} + + +void Explain_union::print_explain_json(Explain_query *query, Json_writer *writer, bool is_analyze, bool no_tmp_tbl) +{ + if (is_pushed_down_to_engine) + print_explain_json_pushed_down(query, writer, is_analyze, no_tmp_tbl); + else + print_explain_json_regular(query, writer, is_analyze, no_tmp_tbl); +} + +/* + Prints EXPLAIN plan in JSON format for a regular UNIT (UNION/EXCEPT/INTERSECT), + i.e. UNIT that has not been pushed down to a storage engine +*/ + +void Explain_union::print_explain_json_regular( + Explain_query *query, Json_writer *writer, bool is_analyze, bool no_tmp_tbl) { Json_writer_nesting_guard guard(writer); char table_name_buffer[SAFE_NAME_LEN]; @@ -755,6 +862,31 @@ void Explain_union::print_explain_json(Explain_query *query, writer->end_object(); } +/* + Prints EXPLAIN plan in JSON format for a UNIT (UNION/EXCEPT/INTERSECT) that + has been pushed down to a storage engine +*/ + +void Explain_union::print_explain_json_pushed_down(Explain_query *query, + Json_writer *writer, + bool is_analyze, + bool no_tmp_tbl) +{ + Json_writer_nesting_guard guard(writer); + + writer->add_member("query_block").start_object(); + + if (is_recursive_cte) + writer->add_member("recursive_union").start_object(); + else + writer->add_member("union_result").start_object(); + + writer->add_member("message").add_str(fake_select_type); + + writer->end_object(); // union_result + writer->end_object(); // query_block +} + /* Print EXPLAINs for all children nodes (i.e. for subqueries) diff --git a/sql/sql_explain.h b/sql/sql_explain.h index 2ab2775820b..146d9527268 100644 --- a/sql/sql_explain.h +++ b/sql/sql_explain.h @@ -336,6 +336,7 @@ public: ///////////////////////////////////////////////////////////////////////////// extern const char *unit_operation_text[4]; +extern const char *pushed_unit_operation_text[4]; extern const char *pushed_derived_text; extern const char *pushed_select_text; @@ -350,7 +351,7 @@ class Explain_union : public Explain_node public: Explain_union(MEM_ROOT *root, bool is_analyze) : Explain_node(root), union_members(PSI_INSTRUMENT_MEM), - is_recursive_cte(false), + is_recursive_cte(false), is_pushed_down_to_engine(false), fake_select_lex_explain(root, is_analyze) {} @@ -381,13 +382,19 @@ public: } int print_explain(Explain_query *query, select_result_sink *output, uint8 explain_flags, bool is_analyze); - void print_explain_json(Explain_query *query, Json_writer *writer, + void print_explain_json(Explain_query *query, Json_writer *writer, bool is_analyze, bool no_tmp_tbl); + void print_explain_json_regular(Explain_query *query, Json_writer *writer, + bool is_analyze, bool no_tmp_tbl); + void print_explain_json_pushed_down(Explain_query *query, + Json_writer *writer, bool is_analyze, + bool no_tmp_tbl); const char *fake_select_type; bool using_filesort; bool using_tmp; bool is_recursive_cte; + bool is_pushed_down_to_engine; /* Explain data structure for "fake_select_lex" (i.e. for the degenerate @@ -406,6 +413,10 @@ public: } private: uint make_union_table_name(char *buf); + int print_explain_regular(Explain_query *query, select_result_sink *output, + uint8 explain_flags, bool is_analyze); + int print_explain_pushed_down(select_result_sink *output, + uint8 explain_flags, bool is_analyze); Table_access_tracker fake_select_lex_tracker; /* This one is for reading after ORDER BY */ diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 5206a43f5eb..ddb843e0a7e 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -5450,12 +5450,18 @@ void st_select_lex::set_explain_type(bool on_the_fly) using_materialization= TRUE; } + if (!on_the_fly) + options|= SELECT_DESCRIBE; + + if (pushdown_select) + { + type= pushed_select_text; + return; + } + if (master_unit()->thd->lex->first_select_lex() == this) { - if (pushdown_select) - type= pushed_select_text; - else - type= is_primary ? "PRIMARY" : "SIMPLE"; + type= is_primary ? "PRIMARY" : "SIMPLE"; } else { @@ -5465,7 +5471,7 @@ void st_select_lex::set_explain_type(bool on_the_fly) if (linkage == DERIVED_TABLE_TYPE) { bool is_pushed_master_unit= master_unit()->derived && - master_unit()->derived->pushdown_derived; + master_unit()->derived->pushdown_derived; if (is_pushed_master_unit) type= pushed_derived_text; else if (is_uncacheable & UNCACHEABLE_DEPENDENT) @@ -5477,13 +5483,10 @@ void st_select_lex::set_explain_type(bool on_the_fly) type= "MATERIALIZED"; else { - if (is_uncacheable & UNCACHEABLE_DEPENDENT) - type= "DEPENDENT SUBQUERY"; - else - { - type= is_uncacheable? "UNCACHEABLE SUBQUERY" : - "SUBQUERY"; - } + if (is_uncacheable & UNCACHEABLE_DEPENDENT) + type= "DEPENDENT SUBQUERY"; + else + type= is_uncacheable ? "UNCACHEABLE SUBQUERY" : "SUBQUERY"; } } else @@ -5506,7 +5509,10 @@ void st_select_lex::set_explain_type(bool on_the_fly) { type= is_uncacheable ? "UNCACHEABLE UNION": "UNION"; if (this == master_unit()->fake_select_lex) - type= unit_operation_text[master_unit()->common_op()]; + type= + master_unit()->pushdown_unit + ? pushed_unit_operation_text[master_unit()->common_op()] + : unit_operation_text[master_unit()->common_op()]; /* join below may be =NULL when this functions is called at an early stage. It will be later called again and we will set the correct @@ -5524,7 +5530,7 @@ void st_select_lex::set_explain_type(bool on_the_fly) pos_in_table_list=NULL for e.g. post-join aggregation JOIN_TABs. */ if (!(tab->table && tab->table->pos_in_table_list)) - continue; + continue; TABLE_LIST *tbl= tab->table->pos_in_table_list; if (tbl->with && tbl->with->is_recursive && tbl->is_with_table_recursive_reference()) @@ -5541,9 +5547,6 @@ void st_select_lex::set_explain_type(bool on_the_fly) } } } - - if (!on_the_fly) - options|= SELECT_DESCRIBE; } @@ -5967,7 +5970,10 @@ int st_select_lex_unit::save_union_explain(Explain_query *output) for (SELECT_LEX *sl= first; sl; sl= sl->next_select()) eu->add_select(sl->select_number); - eu->fake_select_type= unit_operation_text[eu->operation= common_op()]; + eu->is_pushed_down_to_engine= (pushdown_unit != nullptr); + eu->fake_select_type= pushdown_unit ? + pushed_unit_operation_text[eu->operation= common_op()] : + unit_operation_text[eu->operation= common_op()]; eu->using_filesort= MY_TEST(global_parameters()->order_list.first); eu->using_tmp= union_needs_tmp_table(); diff --git a/sql/sql_lex.h b/sql/sql_lex.h index adb887be380..143888a596a 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -38,6 +38,7 @@ #include "sql_schema.h" #include "table.h" #include "sql_class.h" // enum enum_column_usage +#include "select_handler.h" /* Used for flags of nesting constructs */ #define SELECT_NESTING_MAP_SIZE 64 @@ -872,7 +873,7 @@ public: st_select_lex_unit() : union_result(NULL), table(NULL), result(NULL), fake_select_lex(NULL), last_procedure(NULL),cleaned(false), bag_set_op_optimized(false), - have_except_all_or_intersect_all(false) + have_except_all_or_intersect_all(false), pushdown_unit(NULL) { } @@ -937,6 +938,10 @@ public: bool bag_set_op_optimized:1; bool optimize_started:1; bool have_except_all_or_intersect_all:1; + + /* The object used to organize execution of the UNIT by a foreign engine */ + select_handler *pushdown_unit; + /** TRUE if the unit contained TVC at the top level that has been wrapped into SELECT: @@ -1049,6 +1054,7 @@ public: friend class st_select_lex; private: + bool exec_inner(); bool is_derived_eliminated() const; }; @@ -1145,8 +1151,6 @@ public: TABLE_LIST *embedding; /* table embedding to the above list */ table_value_constr *tvc; - /* The interface employed to execute the select query by a foreign engine */ - select_handler *select_h; /* The object used to organize execution of the query by a foreign engine */ select_handler *pushdown_select; List *join_list; /* list for the currently parsed join */ @@ -1616,8 +1620,6 @@ public: uchar *arg); Item *pushdown_from_having_into_where(THD *thd, Item *having); - select_handler *find_select_handler(THD *thd); - bool is_set_op() { return linkage == UNION_TYPE || diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 4a9c50bb2ac..4d23aadbe05 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -71,6 +71,7 @@ #include "derived_handler.h" #include "create_tmp_table.h" #include "optimizer_defaults.h" +#include "derived_handler.h" /* A key part number that means we're using a fulltext scan. @@ -1889,6 +1890,10 @@ int JOIN::optimize() join_optimization_state init_state= optimization_state; if (select_lex->pushdown_select) { + if (optimization_state == JOIN::OPTIMIZATION_DONE) + return 0; + DBUG_ASSERT(optimization_state == JOIN::NOT_OPTIMIZED); + // Do same as JOIN::optimize_inner does: fields= &select_lex->item_list; @@ -4982,9 +4987,20 @@ void JOIN::cleanup_item_list(List &items) const /** @brief - Look for provision of the select_handler interface by a foreign engine + Look for provision of the select_handler interface by a foreign engine. + Must not be called directly, use find_single_select_handler() or + find_partial_select_handler() instead. - @param thd The thread handler + @param + thd The thread handler + select_lex SELECT_LEX object, must be passed in the cases of: + - single select pushdown + - partial pushdown (part of a UNION/EXCEPT/INTERSECT) + Must be NULL in case of entire unit pushdown + select_lex_unit SELECT_LEX_UNIT object, must be passed in the cases of: + - entire unit pushdown + - partial pushdown (part of a UNION/EXCEPT/INTERSECT) + Must be NULL in case of single select pushdown @details The function checks that this is an upper level select and if so looks @@ -4992,18 +5008,21 @@ void JOIN::cleanup_item_list(List &items) const create_select call-back function. If the call of this function returns a select_handler interface object then the server will push the select query into this engine. - This is a responsibility of the create_select call-back function to - check whether the engine can execute the query. + This function does not check if the select has tables from + different engines. Such a check must be done inside each engine's + create_select function. + Also the engine's create_select function must perform other checks + to make sure the engine can execute the query. @retval the found select_handler if the search is successful 0 otherwise */ -select_handler *find_select_handler(THD *thd, - SELECT_LEX* select_lex) +static +select_handler *find_select_handler_inner(THD *thd, + SELECT_LEX *select_lex, + SELECT_LEX_UNIT *select_lex_unit) { - if (select_lex->next_select()) - return 0; if (select_lex->master_unit()->outer_select()) return 0; @@ -5030,13 +5049,47 @@ select_handler *find_select_handler(THD *thd, handlerton *ht= tbl->table->file->partition_ht(); if (!ht->create_select) continue; - select_handler *sh= ht->create_select(thd, select_lex); - return sh; + select_handler *sh= ht->create_select(thd, select_lex, select_lex_unit); + if (sh) + return sh; } return 0; } +/** + Wrapper for find_select_handler_inner() for the case of single select + pushdown. See more comments at the description of + find_select_handler_inner() + +*/ +select_handler *find_single_select_handler(THD *thd, SELECT_LEX *select_lex) +{ + return find_select_handler_inner(thd, select_lex, nullptr); +} + + +/** + Wrapper for find_select_handler_inner() for the case of partial select + pushdown. Partial pushdown means that a unit (i.e. multiple selects combined + with UNION/EXCEPT/INTERSECT operators) cannot be pushed down to + the storage engine as a whole but some particular selects of this unit can. + For example, + SELECT a FROM federated.t1 -- can be pushed down to Federated + UNION + SELECT b FROM local.t2 -- cannot be pushed down, executed locally + + See more comments at the description of find_select_handler_inner() + +*/ +select_handler * +find_partial_select_handler(THD *thd, SELECT_LEX *select_lex, + SELECT_LEX_UNIT *select_lex_unit) +{ + return find_select_handler_inner(thd, select_lex, select_lex_unit); +} + + /** An entry point to single-unit select (a select without UNION). @@ -5146,7 +5199,7 @@ mysql_select(THD *thd, TABLE_LIST *tables, List &fields, COND *conds, thd->get_stmt_da()->reset_current_row_for_warning(1); /* Look for a table owned by an engine with the select_handler interface */ - select_lex->pushdown_select= find_select_handler(thd, select_lex); + select_lex->pushdown_select= find_single_select_handler(thd, select_lex); if ((err= join->optimize())) { @@ -15744,6 +15797,11 @@ void JOIN_TAB::cleanup() } DBUG_VOID_RETURN; } + /*if (table->pos_in_table_list && table->pos_in_table_list->derived) + { + delete table->pos_in_table_list->derived->derived->dt_handler; + }*/ + /* We need to reset this for next select (Tested in part_of_refkey) @@ -30183,7 +30241,6 @@ bool mysql_explain_union(THD *thd, SELECT_LEX_UNIT *unit, select_result *result) DBUG_ENTER("mysql_explain_union"); bool res= 0; SELECT_LEX *first= unit->first_select(); - bool is_pushed_union= unit->derived && unit->derived->pushdown_derived; for (SELECT_LEX *sl= first; sl; sl= sl->next_select()) { @@ -30205,6 +30262,17 @@ bool mysql_explain_union(THD *thd, SELECT_LEX_UNIT *unit, select_result *result) if (!(res= unit->prepare(unit->derived, result, SELECT_NO_UNLOCK | SELECT_DESCRIBE))) { + bool is_pushed_union= + (unit->derived && unit->derived->pushdown_derived) || + unit->pushdown_unit; + if (unit->pushdown_unit) + { + create_explain_query_if_not_exists(thd->lex, thd->mem_root); + if (!unit->executed) + unit->save_union_explain(thd->lex->explain); + List items; + result->prepare(items, unit); + } if (!is_pushed_union) res= unit->exec(); } @@ -30764,7 +30832,7 @@ void st_select_lex::print(THD *thd, String *str, enum_query_type query_type) bool top_level= is_query_topmost(thd); enum explainable_cmd_type sel_type= SELECT_CMD; - if (top_level) + if (top_level && !(query_type & QT_SELECT_ONLY)) sel_type= get_explainable_cmd_type(thd); if (sel_type == INSERT_CMD || sel_type == REPLACE_CMD) diff --git a/sql/sql_union.cc b/sql/sql_union.cc index c1f28baeb25..3c98cfc81c3 100644 --- a/sql/sql_union.cc +++ b/sql/sql_union.cc @@ -32,6 +32,9 @@ #include "sql_cte.h" #include "item_windowfunc.h" +select_handler *find_partial_select_handler(THD *thd, SELECT_LEX *select_lex, + SELECT_LEX_UNIT *lex_unit); + bool mysql_union(THD *thd, LEX *lex, select_result *result, SELECT_LEX_UNIT *unit, ulonglong setup_tables_done_option) { @@ -1282,6 +1285,94 @@ bool init_item_int(THD* thd, Item_int* &item) return true; } +/** + @brief + Recursive subroutine to be called from find_unit_handler() (see below). + Must not be called directly, only from find_unit_handler(). +*/ +static select_handler *find_unit_handler_for_lex(THD *thd, + SELECT_LEX *sel_lex, + SELECT_LEX_UNIT* unit) +{ + if (!(sel_lex->join)) + return nullptr; + for (TABLE_LIST *tbl= sel_lex->join->tables_list; tbl; tbl= tbl->next_local) + { + if (!tbl->table) + continue; + if (tbl->derived) + { + /* + Skip derived table for now as they will be checked + in the subsequent loop + */ + continue; + } + handlerton *ht= tbl->table->file->partition_ht(); + if (!ht->create_unit) + continue; + select_handler *sh= ht->create_unit(thd, unit); + if (sh) + return sh; + } + + for (SELECT_LEX_UNIT *un= sel_lex->first_inner_unit(); un; + un= un->next_unit()) + { + for (SELECT_LEX *sl= un->first_select(); sl; sl= sl->next_select()) + { + select_handler *uh= find_unit_handler_for_lex(thd, sl, unit); + if (uh) + return uh; + } + } + return nullptr; +} + + +/** + @brief + Look for provision of the select_handler interface by a foreign engine. + This interface must support processing UNITs (multiple SELECTs combined + with UNION/EXCEPT/INTERSECT operators) + + @param + thd The thread handler + unit UNIT (one or more SELECTs combined with UNION/EXCEPT/INTERSECT + + @details + The function checks that this is an upper level UNIT and if so looks + through its tables searching for one whose handlerton owns a + create_unit call-back function. If the call of this function returns + a select_handler interface object then the server will push the + query into this engine. + This is a responsibility of the create_unit call-back function to + check whether the engine can execute the query. + + The function recursively scans subqueries (see find_unit_handler_for_lex()) + to get down to real tables and process queries like this: + (SELECT a FROM t1 UNION SELECT b FROM t2) UNION + (SELECT c FROM t3 UNION select d FROM t4) + + @retval the found select_handler if the search is successful + nullptr otherwise +*/ + +static select_handler *find_unit_handler(THD *thd, + SELECT_LEX_UNIT *unit) +{ + if (unit->outer_select()) + return nullptr; + + for (SELECT_LEX *sl= unit->first_select(); sl; sl= sl->next_select()) + { + select_handler *uh= find_unit_handler_for_lex(thd, sl, unit); + if (uh) + return uh; + } + return nullptr; +} + bool st_select_lex_unit::prepare(TABLE_LIST *derived_arg, select_result *sel_result, @@ -1874,6 +1965,13 @@ cont: } } + pushdown_unit= find_unit_handler(thd, this); + if (pushdown_unit) + { + if (unlikely(pushdown_unit->prepare())) + DBUG_RETURN(TRUE); + } + thd->lex->current_select= lex_select_save; DBUG_RETURN(saved_error || thd->is_fatal_error); @@ -2133,6 +2231,15 @@ bool st_select_lex_unit::optimize() (lim.is_unlimited() || sl->braces) ? sl->options & ~OPTION_FOUND_ROWS : sl->options | found_rows_for_union; + if (!this->pushdown_unit) + { + /* + If the UNIT hasn't been pushed down to the engine as a whole, + try to push down partial SELECTs of this UNIT separately + */ + sl->pushdown_select= find_partial_select_handler(thd, sl, this); + } + saved_error= sl->join->optimize(); } @@ -2151,6 +2258,23 @@ bool st_select_lex_unit::optimize() bool st_select_lex_unit::exec() +{ + DBUG_ENTER("st_select_lex_unit::exec"); + if (executed && !uncacheable && !describe) + DBUG_RETURN(FALSE); + + if (pushdown_unit) + { + create_explain_query_if_not_exists(thd->lex, thd->mem_root); + if (!executed) + save_union_explain(thd->lex->explain); + DBUG_RETURN(pushdown_unit->execute()); + } + DBUG_RETURN(exec_inner()); +} + + +bool st_select_lex_unit::exec_inner() { SELECT_LEX *lex_select_save= thd->lex->current_select; SELECT_LEX *select_cursor=first_select(); @@ -2158,10 +2282,7 @@ bool st_select_lex_unit::exec() ha_rows examined_rows= 0; bool first_execution= !executed; bool was_executed= executed; - DBUG_ENTER("st_select_lex_unit::exec"); - if (executed && !uncacheable && !describe) - DBUG_RETURN(FALSE); executed= 1; if (!(uncacheable & ~UNCACHEABLE_EXPLAIN) && item && !item->with_recursive_reference) @@ -2175,7 +2296,7 @@ bool st_select_lex_unit::exec() save_union_explain(thd->lex->explain); if (unlikely(saved_error)) - DBUG_RETURN(saved_error); + return saved_error; if (union_result) { @@ -2192,6 +2313,7 @@ bool st_select_lex_unit::exec() { if (!fake_select_lex && !(with_element && with_element->is_recursive)) union_result->cleanup(); + for (SELECT_LEX *sl= select_cursor; sl; sl= sl->next_select()) { ha_rows records_at_start= 0; @@ -2251,8 +2373,8 @@ bool st_select_lex_unit::exec() { // This is UNION DISTINCT, so there should be a fake_select_lex DBUG_ASSERT(fake_select_lex != NULL); - if (unlikely(table->file->ha_disable_indexes(HA_KEY_SWITCH_ALL))) - DBUG_RETURN(TRUE); + if (unlikely(table->file->ha_disable_indexes(HA_KEY_SWITCH_ALL))) + return true; table->no_keyread=1; } if (likely(!saved_error)) @@ -2262,14 +2384,14 @@ bool st_select_lex_unit::exec() if (union_result->flush()) { thd->lex->current_select= lex_select_save; - DBUG_RETURN(1); + return true; } } } if (unlikely(saved_error)) { thd->lex->current_select= lex_select_save; - DBUG_RETURN(saved_error); + return saved_error; } if (fake_select_lex != NULL) { @@ -2278,7 +2400,7 @@ bool st_select_lex_unit::exec() if (unlikely(error)) { table->file->print_error(error, MYF(0)); - DBUG_RETURN(1); + return true; } } if (found_rows_for_union && !sl->braces && @@ -2421,7 +2543,7 @@ bool st_select_lex_unit::exec() thd->lex->current_select= lex_select_save; err: thd->lex->set_limit_rows_examined(); - DBUG_RETURN(saved_error); + return saved_error; } @@ -2648,6 +2770,9 @@ bool st_select_lex_unit::cleanup() } } + delete pushdown_unit; + pushdown_unit= nullptr; + DBUG_RETURN(error); } @@ -2811,6 +2936,8 @@ bool st_select_lex::cleanup() inner_refs_list.empty(); exclude_from_table_unique_test= FALSE; hidden_bit_fields= 0; + delete pushdown_select; + pushdown_select= nullptr; DBUG_RETURN(error); } diff --git a/sql/table.h b/sql/table.h index d0d6f1e178b..2983c02dcb0 100644 --- a/sql/table.h +++ b/sql/table.h @@ -2540,8 +2540,6 @@ struct TABLE_LIST bool block_handle_derived; /* The interface employed to materialize the table by a foreign engine */ derived_handler *dt_handler; - /* The text of the query specifying the derived table */ - LEX_CSTRING derived_spec; /* The object used to organize execution of the query that specifies the derived table by a foreign engine diff --git a/storage/federatedx/federatedx_pushdown.cc b/storage/federatedx/federatedx_pushdown.cc index e9a9791a859..7615e61ae09 100644 --- a/storage/federatedx/federatedx_pushdown.cc +++ b/storage/federatedx/federatedx_pushdown.cc @@ -34,7 +34,6 @@ set global federated_pushdown=1; */ - /* Check if table and database names are equal on local and remote servers @@ -93,41 +92,97 @@ bool local_and_remote_names_mismatch(const TABLE_SHARE *tbl_share, } +/* + Check that all tables in the sel_lex use the FederatedX storage engine + and return one of them + @return + One of the tables from sel_lex +*/ +static TABLE *get_fed_table_for_pushdown(SELECT_LEX *sel_lex) +{ + TABLE *table= nullptr; + if (!sel_lex->join) + return nullptr; + for (TABLE_LIST *tbl= sel_lex->join->tables_list; tbl; tbl= tbl->next_local) + { + if (!tbl->table) + return nullptr; + if (tbl->derived) + { + /* + Skip derived table for now as they will be checked + in the subsequent loop + */ + continue; + } + + /* + We intentionally don't support partitioned federatedx tables here, so + use file->ht and not file->partition_ht(). + */ + if (tbl->table->file->ht != federatedx_hton) + return nullptr; + const FEDERATEDX_SHARE *fshare= + ((ha_federatedx *) tbl->table->file)->get_federatedx_share(); + if (local_and_remote_names_mismatch(tbl->table->s, fshare)) + return nullptr; + + if (!table) + table= tbl->table; + } + + for (SELECT_LEX_UNIT *un= sel_lex->first_inner_unit(); un; + un= un->next_unit()) + { + for (SELECT_LEX *sl= un->first_select(); sl; sl= sl->next_select()) + { + auto inner_tbl= get_fed_table_for_pushdown(sl); + if (!inner_tbl) + return nullptr; + if (!table) + table= inner_tbl; + } + } + return table; +} + + +/* + Check that all tables in the lex_unit use the FederatedX storage engine + and return one of them + + @return + One of the tables from lex_unit +*/ +static TABLE *get_fed_table_for_unit_pushdown(SELECT_LEX_UNIT *lex_unit) +{ + TABLE *table= nullptr; + for (auto sel_lex= lex_unit->first_select(); sel_lex; + sel_lex= sel_lex->next_select()) + { + auto next_tbl= get_fed_table_for_pushdown(sel_lex); + if (!next_tbl) + return nullptr; + if (!table) + table= next_tbl; + } + return table; +} + + static derived_handler* create_federatedx_derived_handler(THD* thd, TABLE_LIST *derived) { if (!use_pushdown) return 0; - ha_federatedx_derived_handler* handler = NULL; - SELECT_LEX_UNIT *unit= derived->derived; - for (SELECT_LEX *sl= unit->first_select(); sl; sl= sl->next_select()) - { - if (!(sl->join)) - return 0; - for (TABLE_LIST *tbl= sl->join->tables_list; tbl; tbl= tbl->next_local) - { - if (!tbl->table) - return 0; - /* - We intentionally don't support partitioned federatedx tables here, so - use file->ht and not file->partition_ht(). - */ - if (tbl->table->file->ht != federatedx_hton) - return 0; + auto tbl= get_fed_table_for_unit_pushdown(unit); + if (!tbl) + return nullptr; - const FEDERATEDX_SHARE *fshare= - ((ha_federatedx*)tbl->table->file)->get_federatedx_share(); - if (local_and_remote_names_mismatch(tbl->table->s, fshare)) - return 0; - } - } - - handler= new ha_federatedx_derived_handler(thd, derived); - - return handler; + return new ha_federatedx_derived_handler(thd, derived, tbl); } @@ -137,81 +192,23 @@ create_federatedx_derived_handler(THD* thd, TABLE_LIST *derived) */ ha_federatedx_derived_handler::ha_federatedx_derived_handler(THD *thd, - TABLE_LIST *dt) + TABLE_LIST *dt, + TABLE *tbl) : derived_handler(thd, federatedx_hton), - share(NULL), txn(NULL), iop(NULL), stored_result(NULL) + federatedx_handler_base(thd, tbl) { derived= dt; + + query.length(0); + dt->derived->print(&query, + enum_query_type(QT_VIEW_INTERNAL | + QT_ITEM_ORIGINAL_FUNC_NULLIF | + QT_PARSABLE)); } ha_federatedx_derived_handler::~ha_federatedx_derived_handler() = default; -int ha_federatedx_derived_handler::init_scan() -{ - THD *thd; - int rc= 0; - - DBUG_ENTER("ha_federatedx_derived_handler::init_scan"); - - TABLE *table= derived->get_first_table()->table; - ha_federatedx *h= (ha_federatedx *) table->file; - iop= &h->io; - share= get_share(table->s->table_name.str, table); - thd= table->in_use; - txn= h->get_txn(thd); - if ((rc= txn->acquire(share, thd, TRUE, iop))) - DBUG_RETURN(rc); - - if ((*iop)->query(derived->derived_spec.str, derived->derived_spec.length)) - goto err; - - stored_result= (*iop)->store_result(); - if (!stored_result) - goto err; - - DBUG_RETURN(0); - -err: - DBUG_RETURN(HA_FEDERATEDX_ERROR_WITH_REMOTE_SYSTEM); -} - -int ha_federatedx_derived_handler::next_row() -{ - int rc; - FEDERATEDX_IO_ROW *row; - ulong *lengths; - Field **field; - int column= 0; - Time_zone *saved_time_zone= table->in_use->variables.time_zone; - DBUG_ENTER("ha_federatedx_derived_handler::next_row"); - - if ((rc= txn->acquire(share, table->in_use, TRUE, iop))) - DBUG_RETURN(rc); - - if (!(row= (*iop)->fetch_row(stored_result))) - DBUG_RETURN(HA_ERR_END_OF_FILE); - - /* Convert row to internal format */ - table->in_use->variables.time_zone= UTC; - lengths= (*iop)->fetch_lengths(stored_result); - - for (field= table->field; *field; field++, column++) - { - if ((*iop)->is_column_null(row, column)) - (*field)->set_null(); - else - { - (*field)->set_notnull(); - (*field)->store((*iop)->get_column_data(row, column), - lengths[column], &my_charset_bin); - } - } - table->in_use->variables.time_zone= saved_time_zone; - - DBUG_RETURN(rc); -} - -int ha_federatedx_derived_handler::end_scan() +int federatedx_handler_base::end_scan_() { DBUG_ENTER("ha_federatedx_derived_handler::end_scan"); @@ -222,89 +219,116 @@ int ha_federatedx_derived_handler::end_scan() DBUG_RETURN(0); } -void ha_federatedx_derived_handler::print_error(int, unsigned long) -{ -} - -static select_handler* -create_federatedx_select_handler(THD* thd, SELECT_LEX *sel) +/* + Create FederatedX select handler for processing either a single select + (in this case sel_lex is initialized and lex_unit==NULL) + or a select that is part of a unit + (in this case both sel_lex and lex_unit are initialized) +*/ +static select_handler * +create_federatedx_select_handler(THD *thd, SELECT_LEX *sel_lex, + SELECT_LEX_UNIT *lex_unit) { if (!use_pushdown) - return 0; + return nullptr; - ha_federatedx_select_handler* handler = NULL; + auto tbl= get_fed_table_for_pushdown(sel_lex); + if (!tbl) + return nullptr; - for (TABLE_LIST *tbl= thd->lex->query_tables; tbl; tbl= tbl->next_global) - { - if (!tbl->table) - return 0; - /* - We intentionally don't support partitioned federatedx tables here, so - use file->ht and not file->partition_ht(). - */ - if (tbl->table->file->ht != federatedx_hton) - return 0; - - const FEDERATEDX_SHARE *fshare= - ((ha_federatedx*)tbl->table->file)->get_federatedx_share(); - - if (local_and_remote_names_mismatch(tbl->table->s, fshare)) - return 0; - } - - /* - Currently, ha_federatedx_select_handler::init_scan just takes the - thd->query and sends it to the backend. - This obviously won't work if the SELECT uses an "INTO @var" or - "INTO OUTFILE". It is also unlikely to work if the select has some - other kind of side effect. - */ - if (sel->uncacheable & UNCACHEABLE_SIDEEFFECT) + if (sel_lex->uncacheable & UNCACHEABLE_SIDEEFFECT) return NULL; - handler= new ha_federatedx_select_handler(thd, sel); - - return handler; + return new ha_federatedx_select_handler(thd, sel_lex, lex_unit, tbl); } +/* + Create FederatedX select handler for processing a unit as a whole. + Term "unit" stands for multiple SELECTs combined with + UNION/EXCEPT/INTERSECT operators +*/ +static select_handler * +create_federatedx_unit_handler(THD *thd, SELECT_LEX_UNIT *sel_unit) +{ + if (!use_pushdown) + return nullptr; + + auto tbl= get_fed_table_for_unit_pushdown(sel_unit); + if (!tbl) + return nullptr; + + if (sel_unit->uncacheable & UNCACHEABLE_SIDEEFFECT) + return nullptr; + + return new ha_federatedx_select_handler(thd, sel_unit, tbl); +} + + /* Implementation class of the select_handler interface for FEDERATEDX: class implementation */ -ha_federatedx_select_handler::ha_federatedx_select_handler(THD *thd, - SELECT_LEX *sel) - : select_handler(thd, federatedx_hton), - share(NULL), txn(NULL), iop(NULL), stored_result(NULL) +federatedx_handler_base::federatedx_handler_base(THD *thd_arg, TABLE *tbl_arg) + : share(NULL), txn(NULL), iop(NULL), stored_result(NULL), + query(thd_arg->charset()), + query_table(tbl_arg) +{} + +ha_federatedx_select_handler::ha_federatedx_select_handler( + THD *thd, SELECT_LEX *select_lex, TABLE *tbl) + : select_handler(thd, federatedx_hton, select_lex), + federatedx_handler_base(thd, tbl) { - select= sel; + query.length(0); + select_lex->print(thd, &query, + enum_query_type(QT_VIEW_INTERNAL | + QT_ITEM_ORIGINAL_FUNC_NULLIF | + QT_PARSABLE)); } ha_federatedx_select_handler::~ha_federatedx_select_handler() = default; -int ha_federatedx_select_handler::init_scan() +ha_federatedx_select_handler::ha_federatedx_select_handler( + THD *thd, SELECT_LEX_UNIT *lex_unit, TABLE *tbl) + : select_handler(thd, federatedx_hton, lex_unit), + federatedx_handler_base(thd, tbl) { + query.length(0); + lex_unit->print(&query, + enum_query_type(QT_VIEW_INTERNAL | QT_SELECT_ONLY | + QT_ITEM_ORIGINAL_FUNC_NULLIF | + QT_PARSABLE)); +} + +ha_federatedx_select_handler::ha_federatedx_select_handler( + THD *thd, SELECT_LEX *select_lex, SELECT_LEX_UNIT *lex_unit, TABLE *tbl) + : select_handler(thd, federatedx_hton, select_lex, lex_unit), + federatedx_handler_base(thd, tbl) +{ + query.length(0); + select_lex->print(thd, &query, + enum_query_type(QT_VIEW_INTERNAL | QT_SELECT_ONLY | + QT_ITEM_ORIGINAL_FUNC_NULLIF | + QT_PARSABLE)); +} + +int federatedx_handler_base::init_scan_() +{ + THD *thd= query_table->in_use; int rc= 0; DBUG_ENTER("ha_federatedx_select_handler::init_scan"); - TABLE *table= 0; - for (TABLE_LIST *tbl= thd->lex->query_tables; tbl; tbl= tbl->next_global) - { - if (!tbl->table) - continue; - table= tbl->table; - break; - } - ha_federatedx *h= (ha_federatedx *) table->file; + ha_federatedx *h= (ha_federatedx *) query_table->file; iop= &h->io; - share= get_share(table->s->table_name.str, table); + share= get_share(query_table->s->table_name.str, query_table); txn= h->get_txn(thd); if ((rc= txn->acquire(share, thd, TRUE, iop))) DBUG_RETURN(rc); - if ((*iop)->query(thd->query(), thd->query_length())) + if ((*iop)->query(query.ptr(), query.length())) goto err; stored_result= (*iop)->store_result(); @@ -317,7 +341,7 @@ err: DBUG_RETURN(HA_FEDERATEDX_ERROR_WITH_REMOTE_SYSTEM); } -int ha_federatedx_select_handler::next_row() +int federatedx_handler_base::next_row_(TABLE *table) { int rc= 0; FEDERATEDX_IO_ROW *row; @@ -355,19 +379,8 @@ int ha_federatedx_select_handler::next_row() int ha_federatedx_select_handler::end_scan() { - DBUG_ENTER("ha_federatedx_derived_handler::end_scan"); - free_tmp_table(thd, table); table= 0; - (*iop)->free_result(stored_result); - - free_share(txn, share); - - DBUG_RETURN(0); -} - -void ha_federatedx_select_handler::print_error(int error, myf error_flag) -{ - select_handler::print_error(error, error_flag); + return federatedx_handler_base::end_scan_(); } diff --git a/storage/federatedx/federatedx_pushdown.h b/storage/federatedx/federatedx_pushdown.h index 673abcfc68d..c2ce80b0708 100644 --- a/storage/federatedx/federatedx_pushdown.h +++ b/storage/federatedx/federatedx_pushdown.h @@ -14,29 +14,43 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "sql_string.h" #include "derived_handler.h" #include "select_handler.h" +class federatedx_handler_base +{ +protected: + FEDERATEDX_SHARE *share; + federatedx_txn *txn; + federatedx_io **iop; + FEDERATEDX_IO_RESULT *stored_result; + StringBuffer<512> query; + + TABLE *query_table; + + int next_row_(TABLE *tmp_tbl); + int init_scan_(); + int end_scan_(); +public: + federatedx_handler_base(THD *thd_arg, TABLE *tbl_arg); +}; + /* Implementation class of the derived_handler interface for FEDERATEDX: class declaration */ -class ha_federatedx_derived_handler: public derived_handler +class ha_federatedx_derived_handler: public derived_handler, public federatedx_handler_base { private: - FEDERATEDX_SHARE *share; - federatedx_txn *txn; - federatedx_io **iop; - FEDERATEDX_IO_RESULT *stored_result; public: - ha_federatedx_derived_handler(THD* thd_arg, TABLE_LIST *tbl); + ha_federatedx_derived_handler(THD* thd_arg, TABLE_LIST *tbl, TABLE *tbl_arg); ~ha_federatedx_derived_handler(); - int init_scan(); - int next_row(); - int end_scan(); - void print_error(int, unsigned long); + int init_scan() { return federatedx_handler_base::init_scan_(); } + int next_row() { return federatedx_handler_base::next_row_(table); } + int end_scan() { return federatedx_handler_base::end_scan_(); } }; @@ -45,19 +59,17 @@ public: class declaration */ -class ha_federatedx_select_handler: public select_handler +class ha_federatedx_select_handler: public select_handler, public federatedx_handler_base { -private: - FEDERATEDX_SHARE *share; - federatedx_txn *txn; - federatedx_io **iop; - FEDERATEDX_IO_RESULT *stored_result; - public: - ha_federatedx_select_handler(THD* thd_arg, SELECT_LEX *sel); + ha_federatedx_select_handler(THD *thd_arg, SELECT_LEX *sel_lex, + TABLE *tbl); + ha_federatedx_select_handler(THD *thd_arg, SELECT_LEX_UNIT *sel_unit, + TABLE *tbl); + ha_federatedx_select_handler(THD *thd_arg, SELECT_LEX *sel_lex, + SELECT_LEX_UNIT *sel_unit, TABLE *tbl); ~ha_federatedx_select_handler(); - int init_scan(); - int next_row(); + int init_scan() { return federatedx_handler_base::init_scan_(); } + int next_row() { return federatedx_handler_base::next_row_(table); } int end_scan(); - void print_error(int, unsigned long); }; diff --git a/storage/federatedx/ha_federatedx.cc b/storage/federatedx/ha_federatedx.cc index 62a71aa6db6..9a8a2a9044d 100644 --- a/storage/federatedx/ha_federatedx.cc +++ b/storage/federatedx/ha_federatedx.cc @@ -408,8 +408,12 @@ handlerton* federatedx_hton; static derived_handler* create_federatedx_derived_handler(THD* thd, TABLE_LIST *derived); + static select_handler* -create_federatedx_select_handler(THD* thd, SELECT_LEX *sel); +create_federatedx_select_handler(THD *thd, SELECT_LEX *sel_lex, + SELECT_LEX_UNIT *sel_unit); +static select_handler * +create_federatedx_unit_handler(THD *thd, SELECT_LEX_UNIT *sel_unit); /* Federated doesn't need costs.disk_read_ratio as everything is one a remote @@ -458,6 +462,7 @@ int federatedx_db_init(void *p) federatedx_hton->create_derived= create_federatedx_derived_handler; federatedx_hton->create_select= create_federatedx_select_handler; federatedx_hton->update_optimizer_costs= federatedx_update_optimizer_costs; + federatedx_hton->create_unit= create_federatedx_unit_handler; if (mysql_mutex_init(fe_key_mutex_federatedx, &federatedx_mutex, MY_MUTEX_INIT_FAST)) diff --git a/storage/federatedx/ha_federatedx.h b/storage/federatedx/ha_federatedx.h index a67fe1efa8f..cefd8859859 100644 --- a/storage/federatedx/ha_federatedx.h +++ b/storage/federatedx/ha_federatedx.h @@ -467,6 +467,7 @@ public: const FEDERATEDX_SHARE *get_federatedx_share() const { return share; } friend class ha_federatedx_derived_handler; friend class ha_federatedx_select_handler; + friend class federatedx_handler_base; }; extern const char ident_quote_char; // Character for quoting