Skip to content
/ server Public

MDEV-38045: Implement QB_NAME hint with path syntax for nested query blocks#4700

Open
Olernov wants to merge 3 commits intomainfrom
13.0-MDEV-38045-qb-name-path
Open

MDEV-38045: Implement QB_NAME hint with path syntax for nested query blocks#4700
Olernov wants to merge 3 commits intomainfrom
13.0-MDEV-38045-qb-name-path

Conversation

@Olernov
Copy link
Contributor

@Olernov Olernov commented Feb 26, 2026

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

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
@Olernov
Copy link
Contributor Author

Olernov commented Feb 26, 2026

This PR is based on #4692 that is why three commits are displayed. Actually, only the latest commit represent the feature.

…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
@Olernov Olernov force-pushed the 13.0-MDEV-38045-qb-name-path branch from b98f295 to 30a82bb Compare February 26, 2026 15:22
Copy link
Member

@DaveGosselin-MariaDB DaveGosselin-MariaDB left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we guarantee that hint_select_num - 1 >= 0 ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

2 participants