Phase 2 rejected two shapes. A FULL JOIN whose right operand was a
nested join raised ER_FULL_JOIN_BASE_TABLES_ONLY, and a FULL JOIN on the
inner side of an enclosing LEFT or RIGHT JOIN raised
ER_FULL_JOIN_NOT_ALLOWED_IN_OUTER_JOIN. Both shapes are allowed now and
neither error is raised any more.
A FULL JOIN emits the rows of one side that never matched with the other
side null complemented. An enclosing join's condition has to see those
rows complete, which it cannot do while the operand is only a set of
tables spread through the join order. An operand that is a nested join
is therefore computed into a temporary table before the enclosing join
runs, so one JOIN_TAB stands for the whole operand and carries the FULL
JOIN's own marks.
The chosen plan has to keep such an operand on an unbroken span of the
join order, and that span becomes a run of JOIN_TABs of its own, the way
a materialized semi join already is. Nothing outside the operand is
positioned while the operand is computed, so a table inside it can carry
no condition that names an outside table, an equality class that spans
the boundary is split at it, and the outer join scope of a join outside
it stops there. A nest goes on naming a table that table elimination
removed, and an eliminated table is a const table, so the test for
entering the run leaves the eliminated tables out. The test for
entering a materialized semi join run reads the same way.
The operand keeps a copy of its own ON expression, since simplifying the
join tree merges an inner join's ON into the enclosing outer join's ON,
which is not equivalent for a FULL JOIN because it changes which rows of
the right side never match. An operand can itself be built from more
than one join, so an ON expression can sit a level or two below the top
of it, and every such expression is recorded on the operand as a whole,
which is the nest that reads them back. A predicate over the operand is
checked again at the entry standing for it, once the null complementing
has happened. A FULL JOIN's left side WHERE predicates are deferred to
the table that completes its right side even when that side is a nest.
The enclosing ON contributes no access path for the operand.
Reading a row back restores what a nest table cannot hold on its own,
whether the row was null complemented for a column declared NOT NULL,
and the status that says whether the table holds a usable row at all.
The JOIN_TAB standing for a run covers every table of the nest, the
eliminated ones included. The plan output therefore skips only a
JOIN_TAB that is a table of the query, since a JOIN_TAB standing for a
run is never eliminated.
The FULL JOIN code moves out of sql_select.cc, sql_base.cc and table.cc
into opt_full_join.cc and opt_full_join.h. JOIN::full_joins holds a
Full_join_info, declared in opt_full_join_info.h, for each FULL JOIN
that simplify_joins did not rewrite. It holds the two sides, the WHERE
conjuncts held back for the FULL JOIN, the tab that reads its right
side, the duplicate filter of the right rows that matched, and the next
FULL JOIN whose null complement pass runs from the same tab. It
replaces fj_dups, fj_first_target and fj_next_target on JOIN_TAB and
fj_left_cond on TABLE_LIST. The list is built for each JOIN, so a
prepared statement starts every execution with nothing held.
opt_join_runs.cc and opt_join_runs.h hold the runs.
get_best_combination records every run of the chosen join order in a
Join_run_layout before it writes any JOIN_TAB, and the layout gives the
exact number of top level JOIN_TABs. make_join_select walks the plan
with Join_tab_walk, which keeps the tables a run sets aside on entry
until the walk reaches the run's placeholder. A FULL JOIN run can be
inside a materialized semi join, so the walk keeps the runs on a stack.
make_join_select decides again which conditions wait for a FULL JOIN's
right partner, on each condition about to be attached to a table.
Distributing the WHERE builds conditions for one table out of
disjunctions that span both sides, and a HAVING predicate moved into
the WHERE can name the left side alone, so the choice made while the
WHERE is rewritten misses both. That earlier choice stays, since it
keeps the left side out of ref and range analysis. Whether a condition
reads a table after the partner is decided from the join order
positions of the tables it reads.
An enclosing join's ON supplies no key value for a table on a FULL
JOIN's left side, because the null complement pass is complete only
once every left row has been read. A key value that reads only tables
of the same left side is still allowed. An ORDER BY over a FULL JOIN
that simplify_joins kept sorts through a temporary table, because the
null complement pass emits right rows after the scan of the first
table. EXPLAIN gathers the tables of each run at its own depth, and an
operand nest inside a materialized semi join reports under that semi
join's id.
Sections 22 to 28 cover the new shapes. The section covering more than
one matching right side row becomes Section 28, which adds a nest operand
to the same case, and the two sections that came with the fixes before
this one are renumbered to follow it. Sections 35 to 42 cover an ON
expression below the top of an operand, an enclosing ON that keeps the
left side NULLs, operand nests that table elimination shrinks, a
condition offered to a table inside a run, a prepared WHERE that folds
away on a later execution, a FULL JOIN run inside a materialized semi
join, and a left side condition over a table before the partner.
Sections 8, 18 and 27 gain left side predicates that appear only once
the WHERE is rewritten. The helper that gives every column an index of
its own comes with them, ahead of the axis that turns it on.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Attaching a condition to the last table of a join walks back from the end
of the join order looking for the last table of the top level plan. The
search decremented an unsigned index without a lower bound, so a plan
whose last entries all stand for runs of other tables ran the index past
zero and the walk read memory before the array.
The search now stops at the start of the array.
The shape that reaches this is a FULL JOIN with a nested join on both
sides, which is rejected until the phase 2 gates come off, so the
covering test arrives with the queries that shape allows. The defect
itself is older than the FULL JOIN work and does not depend on it.
A ref access guarantees an equality only for the rows it read, so the
optimizer normally drops that equality from the outer join's ON
condition. A table on the left side of a surviving FULL JOIN also
reaches the ON condition as a null row, produced by the pass that emits
the right side rows which never matched on the left, and the ON condition
has to reject those rows. With the equality gone, nothing did, and a row
the outer join owed as a null complement was lost.
The equality now stays in the condition for such a table. The existing
deferral machinery moves it onto the FULL JOIN's right side under the
found match guard, so it is checked once the match has been recorded.
The shape that reaches this is a FULL JOIN whose right side is a nested
join, which is rejected until the phase 2 gates come off, so the covering
test arrives with the queries that shape allows.
The pass that emits the right side rows of a FULL JOIN which never
matched reads the right table with a plain sequential scan rather than
the key lookup the plan chose. A rowid filter belongs to that lookup
because it holds the primary keys the lookup was allowed to return, and
an engine is entitled to assume it never sees one outside an index read.
InnoDB asserts on !prebuilt->index->is_primary() when it does.
The filter is set aside for the length of the scan and put back once the
scan is closed, which is what the executor already does where it swaps a
key lookup for a table scan while running a subquery. Section 24 covers
a plan that reads the right table by key with a filter on a second index.
A FULL JOIN runs as a LEFT JOIN of its left side over its right side,
then a pass emits the right side rows that matched no left row. The pass
reads a record of which right side rows matched, and a row missing from
that record is emitted as a row that matched nothing.
The WHERE predicates on the right side are held back by a guard that
stays closed until the first match of the current left side row, which is
what let the first match be recorded whatever the WHERE then made of the
row. For a second or later matching row of the same left row the guard
is already open, so a WHERE predicate could reject the row before its
match was recorded, and the pass brought it back with the left side all
NULL.
The match is now recorded for every right side row whose match condition
holds. Closing the guard again gives that condition on its own, since
the guard withholds exactly what is no part of the match. Section 23
covers a left row with two matching right rows, one of them rejected by
the WHERE. Before this change that row came back a second time with the
left side NULL, which the UNION of a LEFT JOIN and a RIGHT JOIN does not
produce.
A FULL JOIN's null complement pass marks the whole opposite operand as a
null row before it emits a row of the side it rescans. The walk that
marks the operand treated a TABLE_LIST as either a plain table or a nest.
A merged derived table or view is both, its own placeholder and the
tables it was merged into, so the walk marked the placeholder and
stopped. The query reads the fields of the merged tables, which still
held the values of an earlier row, and those values reached the client
where NULL belonged.
The walk now descends into the nested tables as well as marking the
placeholder. Section 22 covers the shape with the flattening of join
nests turned off, which leaves the derived table in a nest of its own so
the walk reaches it. Before this change the FULL JOIN returned the left
side value 3 against the right side rows 3 and 4, where the UNION of a
LEFT JOIN and a RIGHT JOIN returns NULL.
Four switches, all on by default, so behavior does not change. Each one
turns off a transformation that simplify_joins performs, which helps
narrow down where a wrong result comes from.
full_join_rewrite rewriting a FULL JOIN as a one sided outer join
when the WHERE clause rejects NULLs on one of its
operands. Off keeps the FULL JOIN and forces the
null complement pass to run.
outer_join_to_inner converting an outer join to an inner join when a
conjunctive predicate rejects NULLs for one of
its inner tables.
flatten_join_nests replacing a join nest that carries no ON
expression with its children. Off keeps the
nesting the parser produced, which costs plan
quality without changing results.
simplify_joins an umbrella over the other three.
Each decision moved out of the code that carries it out, so a switch has
one place to read. classify_full_join already held the whole FULL JOIN
decision. The outer join conversion shared a block with work that runs
for a plain inner join as well, so that decision moved to
classify_outer_join, and taking the dependencies of the enclosing nest,
moving the ON expression into the WHERE clause, and dropping the outer
join marks now happen both for a converted join and for one that was
already inner. Registering a semi-join nest moved to
register_semijoin_nest, which always runs since it is the only producer
of the semi-join nest list, leaving the flattening test on its own.
Gating the call to simplify_joins rather than its three decisions would
give wrong results, since that pass is the only producer of the nest
attributes and table dependencies the rest of the optimizer reads.
join_transform_enabled holds the umbrella relationship in one place.
subselect_exists2in turns every switch off by rewriting the whole
optimizer_switch string, so it now turns flatten_join_nests off as well,
and the nest it leaves in place shows up in one line of that test's
expected output. One switch list result file also gained
reorder_outer_joins, which had been left out when that switch was added.
Deciding whether a FULL JOIN can become a one sided outer join was mixed
with work that has to happen whatever that decision is. The descent into
the left operand and the operand swap that the null complement pass
depends on were interleaved with the rewrite itself, so neither could be
turned off on its own.
The decision now lives in classify_full_join, which reads no table state
and changes nothing. The descent moved to
simplify_full_join_left_operand and the survival handling to
keep_full_join. This prepares an optimizer switch that turns the rewrite
off for debugging.
The branch that clears not_null_tables for a surviving FULL JOIN
asserted that conditions had moved out of the left operand, which held
only while the rewrite was always attempted and moved conditions were the
sole reason it could be declined. The clearing is right whatever the
reason was, so the assert is gone and the comment states the invariant
without naming a cause. Behavior is unchanged.
A JOIN_TAB that stands for a run of other JOIN_TABs was only ever the
result of a materialized semi join, and a semi join cannot hold another,
so the functions that walk the join order were written for runs that
never nest. Entering a run descended one level, leaving one popped one
level, and the test for having run off the end compared against the
join's own plan even while the walk was inside a run held elsewhere. The
breadth first walk read a JOIN_TAB with no owning run as being in the
join's own plan, stepped out of a run by assuming the JOIN_TAB the run
hangs from sits in that plan, and searched only that plan for the next
run to enter, so a run held inside another was never reached at all.
The walks now enter and leave as many runs as they stand at the edge of,
and each bounds itself by the run it is actually in. The comparison that
orders two tables by their place in the join order brings both to a
common run before comparing. The breadth first walk works from the run
holding the current JOIN_TAB, steps within that run while it has more,
enters the runs its JOIN_TABs stand for, and when a run has none left to
enter carries on just past the JOIN_TAB that run hangs from.
Nothing produces a nested run yet, so every loop still runs once and the
order is unchanged.
A JOIN_TAB that reads a temporary table standing for several other
tables owned a separately allocated JOIN_TAB_RANGE describing those
tables, and the only way to ask whether a JOIN_TAB had such a range was
to test that pointer. The range is now a member of the JOIN_TAB and
carries the kind of range it is, so the question has a name,
has_bush_children. st_nested_join::nest_type becomes an enumeration for
the same reason.
Asking whether a JOIN_TAB owned a range and asking whether that range
holds the inner tables of a materialized semi join were the same
question, so every place that reaches a semi join through a range's
first JOIN_TAB relied on a materialized semi join being the only thing a
range could hold. is_sjm_nest now reads the kind, and the places that
reach a semi join that way either ask it or assert it. Places that only
care whether a range exists at all keep asking that instead.
A kind is named for the range a materialized FULL JOIN nest will hold,
which an enclosing join's condition needs so that it sees a nest's rows
complete, including the rows the nest produced by null complementing.
Nothing produces one yet, so behaviour is unchanged.
A FULL JOIN runs as a LEFT JOIN of its left side over its right side,
followed by a pass that emits the right rows that never matched a left
row. That second pass is correct only if the first pass records every
left to right match, so it must read every left row and reach the right
side for each match.
A WHERE predicate that references only the left side was applied
directly during the first pass. It pruned left rows in the nested loop,
and it could build a ref or range access on the left side. Either way a
left row was dropped before its match was recorded, and the matching
right row then reappeared in the second pass as a right-only row. In a
FULL JOIN the left side is null complemented in the right-only rows just
as the right side is in the left-only rows, so its predicates are inner
side predicates and must be deferred the same way.
Before access selection, lift the WHERE conjuncts that reference a
surviving FULL JOIN's left side but not its right side out of the WHERE
and hold them on the right side partner. Removed from the WHERE they
build no access on the left side, so it is read in full.
make_join_select reattaches them to the right partner under the found
match guard, so they apply only after the match is recorded. A conjunct
that also references the right side stays in place, since the right side
already defers it.
Also tighten List_iterator::swap_next to assert that it is positioned on
a valid element instead of returning nullptr, since the FULL JOIN
rewrite only calls it in that state.
alloc_full_join_duplicate_filters allocates one duplicate filter for
each right side FULL JOIN table in a range of join tabs. When a later
allocation in the range failed, the filters created earlier in the same
call stayed allocated and leaked.
Free the filters created so far before returning the failure, both when
a recursive call for a bush child fails and when a filter's own
allocation or initialization fails. A failed call now leaves no filters
allocated.
The rewrite of a FULL JOIN to a RIGHT JOIN lost rows when the left
operand was a nested join. rewrite_full_outer_joins recurses into the
left nest, and that recursion can move the ON conditions of the nest's
inner join children into the WHERE clause. Those moved conditions
filter the nest's rows correctly only while the nest stays on the outer
side of the join.
The rewrite to a RIGHT JOIN makes the nest the inner side of the
resulting LEFT JOIN, where the moved conditions reject its null
complemented rows and drop them. Detect the move by comparing the WHERE
pointer before and after the recursion, since every move reassigns it.
When a condition moved, skip the rewrite and let the FULL JOIN survive,
and zero not_null_tables so the caller does not turn the surviving FULL
JOIN into an inner join.
check_full_join_base_tables runs before simplify_joins and rejects the
disallowed FULL JOIN shapes that are visible in the parse tree.
simplify_joins can rewrite a FULL JOIN to a LEFT, RIGHT, or INNER
JOIN, so sometimes disallowed queries appear only afterward.
Add check_full_join_after_simplify, called from optimize_inner once
simplify_joins is done, to reject unsupported queries after
rewrite by simplify_joins.
The outermost FULL JOIN's right operand can be a nested join rather
than a single base table. The parser places the nest on the right
when the outermost FULL JOIN's ON is the last one written, because the
parser keeps the outermost FULL JOIN pending until its ON arrives, and
the inner FULL JOINs reduce first into a nest that becomes the right
operand.
alloc_full_join_duplicate_filters allocates the fj_dups filter on a
JOIN_TAB carrying JOIN_TYPE_FULL | JOIN_TYPE_RIGHT, so with the
FULL|RIGHT bits on the nest, which is never a JOIN_TAB, no filter was
allocated and the null complement pass never fired. The unmatched
rows from the right side were never emitted, producing a result with
missing rows.
Add swap_full_join_sides, called from rewrite_full_outer_joins
when a FULL JOIN survives simplify_joins with a leaf on the left
and a nested join on the right. FULL JOIN is symmetric on its
operands, so swapping does not change query semantics; after the
swap the leaf carries the FULL|RIGHT bits and the rescan target
is a single base table.
Prevent simplify_joins from rewriting a chained FULL JOIN into a query
where a FULL JOIN could end up on the inner side of another outer
join. Of course, this means that we will have a null complement pass
that the rewritten query would have avoided. Once we support FULL
JOINs on the inner side of outer joins, in phase 3, then we can relax
this constraint.
If a table that's in a FULL OUTER JOIN is found to be a const
table, then don't allow the constant table optimization to
take place.
Later, when we support FULL OUTER JOIN on the inner side of
other join types then we may be able to relax this restriction.
In phase 1, FULL [OUTER] JOIN was only supported when simplify_joins()
could rewrite it into an equivalent LEFT, RIGHT, or INNER JOIN based
on NULL-rejecting WHERE predicates. Queries that could not be
rewritten raised ER_NOT_SUPPORTED_YET. (Phase 1 was not released.)
This commit removes that restriction by adding proper support for FULL
JOIN by executing a 'LEFT JOIN pass' that emits matched rows and left
null-complemented rows, then a second "null-complement" pass which
rescans the right table to emit null-complement rows that were never
matched.
FULL JOIN supports nested joins on the left of the FULL JOIN,
NATURAL FULL JOIN, semi-joins, CTEs / derived tables (kept
materialized when they participate in a FULL JOIN), prepared
statements, stored procedures, and aggregates. Examples:
SELECT * FROM (d1 FULL JOIN d2 ON d1.a = d2.a)
FULL JOIN t3 ON d1.a = t3.a;
SELECT * FROM t1 NATURAL FULL JOIN t2;
SELECT * FROM t1 INNER JOIN t2 FULL JOIN t3 ON t1.a = t3.a;
PREPARE st FROM
'SELECT COUNT(*) FROM t1 FULL JOIN t2 ON t1.a = t2.a';
Limitations:
- Statistics and cost estimates for the null-complement pass have
not been fully implemented; the optimizer may under- or
over-estimate FULL JOIN costs in plans involving multiple
FULL JOINs. Again, a follow-up will optimize the cost calculations.
- Optimizations for constant tables not fully supported.
- Nested tables on the right side of a FULL JOIN are not yet supported.
Fetches the ON condition from the FULL OUTER JOIN as the searchable condition.
We ignore the WHERE clause here because we don't want accidental conversions
from FULL JOIN to INNER JOIN during, for example, range analysis, as that
would produce wrong results.
GCOV shows that existing FULL OUTER JOIN tests exercise this new codepath.
Move the temporary gate against FULL OUTER JOIN deeper into the
codebase, which causes the FULL OUTER JOIN query plans to have
more relevant information (hence the change). In some cases, the
join order of nested INNER JOINs within the FULL OUTER JOIN changed.
Small cleanups in get_sargable_cond ahead of the feature work in
the next commit.
Prevent elimination of tables participating in a FULL OUTER JOIN during
eliminate_tables as part of phase one FULL OUTER JOIN development.
Move the functionality gate for FULL JOIN further into the codebase.
Fixes an old bug where, when running the server as a debug build and in
debug mode, a null pointer deference in
Dep_analysis_context::dbug_print_deps would cause a crash.
FULL JOIN yields result sets with columns from both tables participating in
the join (for the sake of explanation, assume base tables). However,
NATURAL FULL JOIN should show unique columns in the output.
Given the following query:
SELECT * FROM t1 NATURAL JOIN t2;
transform it into:
SELECT COALESCE(t1.f_1, t2.f_1), ..., COALESCE(t1.f_n, t2.f_n) FROM
t1 NATURAL JOIN t2;
This change applies only in the case of NATURAL FULL JOIN. Otherwise,
NATURAL JOINs work as they have in the past, which is using columns
from the left table for the resulting column set.
Rewrite FULL OUTER JOIN queries as either LEFT, RIGHT, or INNER JOIN
by checking if and how the WHERE clause rejects nulls.
For example, the following two queries are equivalent because the
WHERE condition rejects nulls from the left table and allows matches
in the right table (or NULL from the right table) for the remaining
rows:
SELECT * FROM t1 FULL JOIN t2 ON t1.v = t2.v WHERE t1.v IS NOT NULL;
SELECT * FROM t1 LEFT JOIN t2 ON t1.v = t2.v;
SELECT * FROM t1 FULL JOIN t2 ON t1.v = t2.v WHERE t1.a=t2.a;
SELECT * FROM t1 INNER JOIN t2 ON t1.v = t2.v WHERE t1.a=t2.a;
Allow FULL OUTER JOIN queries to proceed through name resolution.
Permits limited EXPLAIN EXTENDED support so tests can prove that the
JOIN_TYPE_* table markings are reflected when the query is echoed back by the
server. This happens in at least two places: via a Warning message during
EXPLAIN EXTENDED and during VIEW .frm file creation.
While the query plan output is mostly meaningless at this point, this
limited EXPLAIN support improves the SELECT_LEX print function for the new
JOIN types.
TODO: fix PS protocol before end of FULL OUTER JOIN development
There are 2 problems: -
1. The REPLACE statement that is recorded doesn't store the
value of geometry type field correctly.
2. The table definition that got recorded has fields with non-null constraint,
and no default value is specified.
Also, the "REPLACE INTO" statement that gets stored in the context,
doesn't have any value specified for these non-null fields.
Solution is to: -
1. When using REPLACE INTO statement, store all the non-numeric values in HEX,
whenever conversion from field's charset to output's charset is lossy.
2. Instead of storing only the column values that were projected in the
query, store all the non-virtual column values into the recorded
REPLACE INTO statement.
Implementation details: -
1. Introduce a new method is_charset_conversion_lossless() in filesort.cc,
to check if the output charset to which field's data is being written to,
results in a lossless conversion. If so, non-numeric values being witten
using REPLACE INTO statement are stored in string representation,
else they are converted to HEX.
2. Modify join_read_const(), and join_read_system() methods in sql_select.cc,
and opt_sum_query() method in opt_sum.cc the following way: -
a. Extend the read_set to make sure, we read all the non-virtual column
using Optimizer_context_recorder::prepare_captured_row_read().
This method also saves the original read_set.
b. Read the row.
c. Dump the row into the context when no error is noticed while
reading. Irrespective of the error, restore back the read_set state to
the original using Optimizer_context_recorder::finish_captured_row_read()
Functions that record actions on base tables accept TABLE* argument and
use table->s->{db,table_name} as the table's name.
The only different usage is in store_optimizer_context() where
we encounter a TABLE_LIST object which may be a base table or a VIEW.
- Introduce init_optimizer_context_recorder_if_needed() which is called
at query start. We n longer create the recorder lazily.
- Recorder functions no longer take MEM_ROOT to store data on as an
argument. The recorder knows the MEM_ROOT it should use.
1. Add escaping in strings if they contain quotes in the names
2. Record CONST insert statement for MYISAM system table
3. Add tests to oad the sql_script file that is produced with
record_context=ON and then use it to run same queries on empty tables.
This task loads the stats of the tables that are used in a query,
from the trace into the optimizer. This feature is controlled
by optimizer_replay_context, and points to an user defined variable name,
that has the context information.
The stats such as num_of_records present in the table, indexes if present,
their names, along with the average number of records_per_key
with in each index, cost of reading an index are loaded from the trace.
Additionally, stats from range analysis i.e. ranges, and the
corresponding number of records, along with their costs are also
loaded from the trace.
The trace context which is in JSON format is firstly set into the
user defined session variable, and this variable name is set to
optimizer_replay_context system variable.
Later, when a user issues a query, the contents of the json context is
parsed and an in memory representation is built using the class
Optimizer_context_replay. This class is then used by the optimizer
to update and save original values of the tables, indexes, and range stats
in the methods "set_statistics_for_table()" of sql_statistics.cc, and
"check_quick_select()" of opt_range.cc.
After the query gets finished, the statistics that were updated
in the optimizer are restored back to the saved original values.
The entry point for parsing the json structure is in
"mysql_execute_command()" of sql_parse.cc, and similarly exit point
i.e. to restore the saved stats of the optimizer is at the end
of the same method.
Two new warning/error messages are introduced:
ER_JSON_OPTIMIZER_REPLAY_CONTEXT_PARSE_FAILED, and
ER_JSON_OPTIMIZER_REPLAY_CONTEXT_MATCH_FAILED
When starting to do a full table/index scan without a WHERE or JOIN
condition, tell the storage engine so and the corresponding
ulong-truncated LIMIT.
Include an innodb implementation: added an innodb switch
table_lock_on_full_scan, so that when the switch is on, on receiving
the full scan signal from the sql layer, if the truncated LIMIT is
ULONG_MAX (likely no LIMIT), attempt to acquire a table lock.
Updated tests that have different results with the switch on.
The three deadlock_*_race tests cannot reach their DEBUG_SYNC race
under a table lock (it degenerates to a timeout), and the three I_S
tests only restate a lock-mode change already covered
by innodb_full_scan.test.
(Comment and code edited by Sergei Petrunia <sergey@mariadb.com> and
Thirunarayanan Balathandayuthapani <thiru@mariadb.com>)
When a NATURAL JOIN operates on a derived table (or view with
derived_merge=off) having more than 64 columns, the optimizer's
generate_derived_keys_for_table() would:
1) Overflow a 32-bit shift: (key_part_map)(1 << parts) when parts >= 32
2) Create a derived key with more parts than Bitmap<64>/key_part_map can hold
3) Crash on DBUG_ASSERT(prefix_size <= width) in Bitmap<64>::is_prefix(65)
Fix: cap the number of hash-join KEYUSE entries for a materialized derived
table at the handler's max_key_parts(). Excess columns are excluded from the
key, so generate_derived_keys_for_table() never builds a key with more parts
than key_part_map (64 bits) or Bitmap<64> can represent. Also fix the shift
to cast before shifting: (key_part_map) 1 << parts.
This patch to mysqldump is only merged partially:
commit 666ff1db36
Author: Monty <monty@mariadb.org>
Date: Fri May 15 16:39:35 2026 +0300
MDEV-39519 Change mariadb-dump to use SHOW REPLICA STATUS instead of SHOW SLAVE STATUS
The functionality for --dump-slave cannot be merged as the underlying
functionality was removed in this commit:
commit 21c940d599
Author: ParadoxV5 <paradox.ver5@gmail.com>
Date: Thu Dec 4 21:22:20 2025 -0700
MDEV-37146 mariadb-dump: Replace SHOW SLAVE STATUS usage with information_schema.SLAVE_STATUS
Signed-off-by: Kristian Nielsen <knielsen@knielsen-hq.org>