MDEV-38045: Implement QB_NAME hint with path syntax for nested query blocks#4700
MDEV-38045: Implement QB_NAME hint with path syntax for nested query blocks#4700
Conversation
1. `TABLE_LIST::init_derived` is called twice during derived tables processing: first time from `mysql_derived_init` and the second time from `mysql_derived_prepare`. Both times there is a check for whether an optimizer hint or switch setting allows a derived table to be merged. However, it is not necessary to restrict merging during the initialization call, it is enough to apply hints/switch setting during the preparation phase. 2. `open_normal_and_derived_tables()` runs processing of all phases of derived tables handling with a single call. But for future commits it is required to separate DT initialization from other phases. This commit implements the separation.
This patch implements support for implicit query block (QB) names in optimizer hints, allowing hints to reference query blocks and tables within derived tables, views and CTEs without requiring explicit QB_NAME hints. Examples. -- Addressing a table inside a derived table using implicit QB name select /*+ no_index(t1@dt) */ * from (select * from t1 where a > 10) as DT; -- this is an equivalent to: select /*+ no_index(t1@dt) */ * from (select /*+ qb_name(dt)*/ * from t1 where a > 10) as DT; -- Addressing a query block corresponding to the derived table select /*+ no_bnl(@dt) */ * from (select * from t1, t2 where t.1.a > t2.a) as DT; -- View create view v1 as select * from t1 where a > 10 and b > 100; -- referencing a table inside a view by implicit QB name: select /*+ index_merge(t1@v1 idx_a, idx_b) */ * from v1, t2 where v1.a = t2.a; -- equivalent to: create view v1 as select /*+ qb_name(qb_v1) */ * from t1 where a > 10 and b > 100; select /*+ index_merge(t1@qb_v1 idx_a, idx_b) */ * from v1, t2 where v1.a = t2.a; -- CTE with aless100 as (select a from t1 where b <100) select /*+ index(t1@aless100) */ * from aless100; -- equivalent to: with aless100 as (select /*+ qb_name(aless100) */ a from t1 where b <100) select /*+ index(t1@aless100) */ * from aless100; Limitations: - Only SELECT statements support implicit QB names. DML operations (UPDATE, DELETE, INSERT) only support explicit QB names
|
This PR is based on #4692 that is why three commits are displayed. Actually, only the latest commit represent the feature. |
23f0615 to
b98f295
Compare
…blocks
Extended QB_NAME hint to support path-based addressing of query blocks
nested within views, derived tables, and CTEs, following TiDB's syntax.
New syntax:
QB_NAME(name, query_block_path), where
query_block_path ::= query_block_path_element
[ {, query_block_path_element }... ]
query_block_path_element ::= @ qb_path_element_select_num |
qb_path_element_view_sel
qb_path_element_view_sel ::= qb_path_element_view_name
[ @ qb_path_element_select_num ]
For example,
`SELECT /*+ qb_name(qb_v1, v1) */* FROM v1`
The name `qb_v1` is assigned to the inner query block of the view `v1`.
`SELECT /*+ qb_name(qb_v1, v1@sel_1) */* FROM v1`
Means the same but specifies that `v1` is present in SELECT#1 of the current
query block.
`SELECT /*+ qb_name(qb_v1, v1@sel_1 .@sel_2) */* FROM v1`
This means SELECT#2 of view `v1`, which is present in SELECT#1 of
the current query block, gets the name `qb_v1`.
It is possible to specify not only view names but also derived tables
and CTE's in the path.
Views and derived tables may be nested on multiple levels, for example:
`SELECT /*+ qb_name(dt2_dt1_v1_1, dt1 .dt2 .v2 .@SEL_2)
no_index(t1@dt2_dt1_v1_1)*/ v1.*
FROM v1 JOIN (SELECT v1.* FROM v1 JOIN (SELECT * FROM v2) dt2) dt1`
Limitations:
- Only SELECT statements support QB names with path. DML operations
(UPDATE, DELETE, INSERT) only support explicitly defined QB names
b98f295 to
30a82bb
Compare
DaveGosselin-MariaDB
left a comment
There was a problem hiding this comment.
Impressive test cases and documentation. Found just a couple places where we may be able to improve test coverage.
| // Move up to the outer SELECT | ||
| sl= sl_unit->outer_select(); | ||
| } | ||
| return false; |
There was a problem hiding this comment.
None of the new tests (main.opt_hints, main.opt_hints_impl_qb_name, main.opt_hints_qb_name_path) result in is_descendant_of_unit returning false (taking this path). Yet the code path at line 837 where target_select is NULL is exercised by other cases, so I don't think there's a problem at hand. But is it possible to construct a test case for this path?
There was a problem hiding this comment.
Good observation, I'll add a test case to cover this branch.
| Parse_context target_pc(pc->thd, target_select); | ||
| target_qb= get_qb_hints(&target_pc); | ||
| if (!target_qb) | ||
| return true; |
There was a problem hiding this comment.
This branch also isn't exercised by the tests, and the place where this is (ultimately) called from is LEX::resolve_optimizer_hints but that call site doesn't inspect the result. I'm not sure what we should do, should we call print_warn here (or in resolve_optimizer_hints)?
| { | ||
| const Query_block_path &qb_path= *this; | ||
| if (qb_path.is_empty()) // Simple hint, e.g. QB_NAME(qb1) | ||
| return; |
There was a problem hiding this comment.
Is it possible to exercise this code path?
| } | ||
|
|
||
| const Lex_ident_sys qb_name_sys= Query_block_name::to_ident_sys(pc->thd); | ||
| uint hint_select_num= atoi(select_num_str.str + format.length); |
There was a problem hiding this comment.
Is it possible for hint_select_num to be zero?
| */ | ||
| st_select_lex_unit *unit= target_select->master_unit(); | ||
| uint base_select_num= unit->first_select()->select_number; | ||
| uint target_select_num= base_select_num + (hint_select_num - 1); |
There was a problem hiding this comment.
Can we guarantee that hint_select_num - 1 >= 0 ?
Extended QB_NAME hint to support path-based addressing of query blocks
nested within views, derived tables, and CTEs, following TiDB's syntax.
New syntax:
For example,
SELECT /*+ qb_name(qb_v1, v1) */* FROM v1The name
qb_v1is assigned to the inner query block of the viewv1.SELECT /*+ qb_name(qb_v1, v1@sel_1) */* FROM v1Means the same but specifies that
v1is present in SELECT#1 of the currentquery block.
SELECT /*+ qb_name(qb_v1, v1@sel_1 .@sel_2) */* FROM v1This means SELECT#2 of view
v1, which is present in SELECT#1 ofthe current query block, gets the name
qb_v1.It is possible to specify not only view names but also derived tables
and CTE's in the path.
Views and derived tables may be nested on multiple levels, for example:
SELECT /*+ qb_name(dt2_dt1_v1_1, dt1 .dt2 .v2 .@SEL_2) no_index(t1@dt2_dt1_v1_1)*/ v1.* FROM v1 JOIN (SELECT v1.* FROM v1 JOIN (SELECT * FROM v2) dt2) dt1Limitations:
(UPDATE, DELETE, INSERT) only support explicitly defined QB names