Home - Waterfall Grid T-Grid Console Builders Recent Builds Buildslaves Changesources - JSON API - About

Console View


Categories: connectors experimental galera main
Legend:   Passed Failed Warnings Failed Again Running Exception Offline No data

connectors experimental galera main
Dave Gosselin
MDEV-40620:  Let the join order walks cross nested runs

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.
Dave Gosselin
Split out the SJM and FULL JOIN run-opening code in get_best_combination

The loop that sets up the join order used to have the code for opening
an SJ-Materialization run and the code for opening a FULL JOIN operand
nest run written directly inside it, side by side.  Both blocks are now
their own functions, open_sjm_run and open_full_join_nest_run, called
from the same spot in the loop.

This does not change behavior.  It only makes the two blocks easier to
read on their own and easier to compare against each other, since they
now have matching shapes.
Dave Gosselin
Look up a FULL JOIN's left-most JOIN_TAB directly instead of walking

find_left_most_join_tab used to walk backward from the FULL JOIN's
right side JOIN_TAB, testing each preceding one to see if it belonged
to the left side, until it fell off the left side's edge.

Most of the time this is unnecessary.  A left side that is a single
table already has its own JOIN_TAB, reachable straight from the
table.  A left side of two or more tables is always computed into its
own run, so that run's placeholder JOIN_TAB is now recorded on the
nest itself and can be read off directly.  The walk is still needed
for the rare remaining case, where table elimination has reduced the
left side to one surviving table without giving it a run of its own.

The walk that finds where a FULL JOIN's left side begins in the join
order runs only when table elimination has reduced the left operand to
one surviving table, and no query in full_join.test had that shape.
Elimination reaches inside a FULL JOIN's left operand because that nest
carries no ON of its own, so a LEFT JOIN written there can lose its
inner table.  Section 37 covers it, once with one table eliminated and
once with two.
Dave Gosselin
MDEV-40620:  Drop the rowid filter for the FULL JOIN null complement scan

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.
Dave Gosselin
MDEV-39936:  Preserve a FULL JOIN when its left nest moves conditions

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.
Dave Gosselin
MDEV-40620:  Run the FULL JOIN tests over more engines and switches

full_join.test runs once for every point of three crossed axes, the
optimizer switch axis in full_join.combinations, the storage engine axis
in full_join_engine.combinations and the index axis in
full_join_index.combinations, thirty runs in all.  The result file in the
tree is the run with the default engine, no added indexes and the
switches at their defaults.  Every other run's expected output is a diff
against it, so a run whose output matches it has no file of its own, and
a query added to the test shows up in all thirty runs.

The index axis gives every column of every base table a non-unique index
on itself.  The work is decided by what the database holds at the point
full_join_add_indexes.inc is sourced, so a section sources it after the
tables it creates.  A non-unique index is the only kind that can be added
without knowing the data, since a primary key or a unique index would
fail on the duplicates and the NULLs that many sections put in a join
column on purpose.

The switch axis holds the four switches at their defaults and each of
them off in turn, which is what makes a wrong result attributable to one
transformation.
Dave Gosselin
Update table_elim for FULL JOIN base table check

Two EXPLAIN queries in table_elim place a nested join on the right
side of a FULL JOIN.  Phase 2 supports only base tables there, so
check_full_join_base_tables rejects them with
ER_FULL_JOIN_BASE_TABLES_ONLY.
Sergei Golubchik
MDEV-40608 MariaDB-devel is incomplete for plugins

* create and install mariadb-plugin-config.cmake
* deb: move all headers that plugins need to libmariadb-dev,
  together with libmysqlservices.a. At least until we'll
  create mariadb-plugin-dev.Nobody should need huge
  libmariadbd-dev to develop a plugin
* rpm: all in MariaDB-devel already, no changes here
* adjust plugin.cmake to work for external plugins
* move server-internal part to top-level CMakeLists.txt
* remove WITH_WSREP from my_config.h (it upsets external plugins)
* remove double-defined macros from unireg.h (the guard doesn't help
  if unireg.h is included first)

ColumnStore, until fixed, needs a backward-compatibility workaround
Dave Gosselin
MDEV-38508: Constant table detection

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.
Dave Gosselin
MDEV-XXXXX:  Save and restore the status of tables inside a run

Walking back over the tables a join buffer scan feeds from takes a copy
of each table's status and clears it, then puts the copy back when the
scan closes.  For a table standing for a run of other tables, the tables
in that run were left out of the copy but were still written to when the
copy was put back, so they received a value the copy had never taken.
They now take part in both directions.

The work for one table moved into a function that reaches the tables of
any run it stands for, however deeply those runs nest, which is what the
rest of the join order walking already does.

This is a defect in the handling of a materialized semi join, which is
the only kind of run a released version produces, so it is not specific
to FULL JOIN.  No query is known to reach it today.  The FULL JOIN work
that follows produces runs that nest, which reaches it.
Dave Gosselin
MDEV-40620:  Record every FULL JOIN match on the right side

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.
Oleg Smirnov
Distribute parallel work more evenly

Reduce SPLIT_THRESHOLD and improve the chunk queue logic
Vladislav Vaintroub
MDEV-40608 install mariadb-plugin-config.cmake on Windows too

This only makes the file appear in the package; it does not yet make
its content Windows-correct (relocatable prefix, exported "server"
import lib for storage engines, etc.) - those are separate follow-ups.

Assisted-by: Claude:claude-5-sonnet
Jaeheon Shim
MDEV-40698 Fix ROLLUP query results with empty result set

ROLLUP is defined as the UNION of grouping by every prefix of fields in
the original GROUP BY list. A ROLLUP query with an empty result set
returned zero rows when it should return a summary NULL, since grouping
by the empty prefix returns a single summary row.

The empty row case is handled in two separate locations. First, if the
optimizer is able to determine that no rows will be produced, e.g. due
to the table being empty or the WHERE condition resolving to false,
JOIN::send_row_on_empty_set is used to determine whether or not to send
an empty result row. Therefore, send_row_on_empty_set is modified to
include select_lex->olap == ROLLUP_TYPE.

Second, it may be the case that the absence of rows is not confirmed
until the execution phase. For instance when the WHERE condition is not
constant, or in the case of InnoDB where an empty table is not detected
during optimization. This is handled in both end_send_group and
end_write_group by this expression

    join->first_record ||
        (end_of_records && !join->group && !join->group_optimized_away)

The condition is extracted into need_empty_set_row and a second variable
empty_set_send_rollup_total is recorded to prevent running the default
rollup_send_data/rollup_write_data on the empty row case. This is
because the null summary row is already handled by send_data_with_check.
Dave Gosselin
BUGFIX:  Hold FULL JOIN left side conditions until the match

A predicate over the left side of a surviving FULL JOIN has to wait for
the right partner, where the match is recorded.  Choosing which
predicates those are while the WHERE is still being rewritten misses
two kinds.  Distributing the WHERE over the plan builds a condition for
a single table out of a disjunction that spans both sides, and that
condition was never a conjunct of the WHERE.  A HAVING predicate moved
into the WHERE, or a disjunct that folds away, can also leave an
equality over the left side behind after the choice was already made.

Choose again in make_join_select, on whatever condition is about to be
attached to a table, and hold it on the right partner instead.  The
earlier pass stays, since it is what keeps the left side out of ref and
range analysis.
Dave Gosselin
Reject a nested join on the right of a rewritten FULL 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.
Dave Gosselin
MDEV-39014: FULL JOIN Phase 2

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.
Dave Gosselin
MDEV-39936:  Defer left side WHERE predicates of a surviving FULL JOIN

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.
Dave Gosselin
MDEV-38502: FULL OUTER JOIN get correct searchable condition

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.
Dave Gosselin
MDEV-40620:  Allow a nested join as a FULL JOIN operand

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.

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, 38, and 39 cover an
ON expression below the top of an operand, an eliminated table inside an
operand nest, and a condition offered to a table inside a run.  The
helper that gives every column an index of its own comes with them,
ahead of the axis that turns it on.
Dave Gosselin
MDEV-40620:  Split the FULL JOIN rewrite decision from its bookkeeping

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.
Dave Gosselin
MDEV-XXXXX:  Keep the last top level base table search in bounds

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.
Dave Gosselin
MDEV-39967:  no bug, but preserve test case
Dave Gosselin
Prototype version of FULL OUTER JOIN

This demonstrates FULL OUTER JOIN with nests on either side

There are bugs in the implementation at this point, development ongoing

(this commit intentionally empty)
Dave Gosselin
MDEV-39569: Skip FULL JOIN rewrite to inner side of an outer join

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.
Sergei Golubchik
fix `cmake --install .` to install where mariadb-plugin-config.cmake is

same basedir, that is
Sergei Golubchik
fix debian versioning
Dave Gosselin
MDEV-40620:  Null complement a merged derived table inside a FULL JOIN

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.
Dave Gosselin
MDEV-39936:  Free FULL JOIN duplicate filters on allocation failure

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.
Dave Gosselin
Address Monty's Phase 2 Review Feedback
Sergei Golubchik
for tar.gz installations: specify where to find libmariadbservices.a
Dave Gosselin
Move FULL JOIN code to separate files
Dave Gosselin
Remove unused errors
Alessandro Vetere
MDEV-38056 Assertion 'bpage->state() >= buf_page_t::UNFIXED' in buf_page_get_zip()

Three callers reconstruct old versions of a clustered index record, derive
secondary index entries from them, and dereference the BLOB pointers of
externally stored columns: row_vers_impl_x_locked_low() for the implicit lock
check, row_undo_mod_sec_is_unsafe() for rollback, and row_check_index() for
CHECK TABLE ... EXTENDED. purge_sys.view is what keeps those pages allocated,
but purge_sys_t::view_guard froze it only for the duration of
trx_undo_prev_version_build(), and the dereference happens after. On
ROW_FORMAT=COMPRESSED this trips the assertion above; in a release build the
freed page is read anyway, which is silent corruption.

All three now hold purge_sys.latch across the dereference, and only where the
version has externally stored columns, since row_build() dereferences nothing
otherwise. In row_check_index() the freeze spans the whole comparison, which
fetches one field at a time and may evaluate a virtual column expression; for
the latest version it is also confined to a delete-marked record, the only
kind that need not own what it points at. Freezing that late means the view
may have advanced since the walk decided a version was reachable, so each
caller re-establishes that under the freeze; trx_undo_prev_version_build()
states the condition and why testing the oldest writer applied suffices.

The first two callers can treat that test as an invariant and end the walk
where it fails. row_check_index() cannot, because it decides reachability from
the lagging purge_sys.end_view on purpose, and that lag is how it finds orphan
secondary index records: where the test fails it stops and reports nothing
rather than treating the record as an orphan, and no report is lost for good,
end_view eventually reaching the same point. Its two purge_sys.is_purgeable()
tests now read the frozen view wherever a fetch follows, which makes them
atomic with the fetch they guard.

trx_undo_prev_version_build(): remove the gate that was meant to stop CHECK
TABLE ... EXTENDED from fetching BLOBs it may no longer own, and
view_guard::is_extended() with it, which no guard mode could satisfy. That
decision belongs to the caller, the only one that knows whether it will
dereference anything.

row_log_table_get_pk(): document why the online ALTER path may dereference
without a freeze.

Debug-only keywords. purge_hold_cleanup parks a purge batch between its last
purged record and purge_sys_t::batch_cleanup(), the window in which a reader
that goes by purge_sys.end_view can still reach history the batch has removed;
a batch opens and closes it without ever returning to the test.
purge_no_blob_freeze sends a version that does have externally stored columns
down the path one without any takes, which is what all four call sites did
before this change, and reproduces the assertion above.
row_vers_impl_x_locked_purgeable, row_undo_mod_sec_is_unsafe_purgeable and
row_check_index_purgeable force the re-validation to fail, reaching exits that
purge_sys.view advancing mid-walk otherwise produces. The first reports no
implicit lock for a row that a live transaction still holds, so a test may
only check that nothing breaks; the other two make the server more cautious.

Tests. old_blob and old_blob_updel cover the implicit lock check,
old_blob_rollback the rollback, old_blob_check CHECK TABLE ... EXTENDED. Each
parks a walk at a dereference, makes the BLOB freeable, and asserts that the
counter of purged update records, which is what would free it, stays at zero
while parked and advances once the walk is over. old_blob_updel covers an undo
log record that stores only the 20-byte reference and needs purge_hold_cleanup
to reach it; old_blob_rollback parks at a reference that the version merely
inherited. All four fail with the original assertion under
debug_dbug=+d,purge_no_blob_freeze, and skip above a 16k page size, which
ROW_FORMAT=COMPRESSED requires. old_blob_purgeable drives the three
re-validation exits, needs no synchronisation, and runs at every page size.
Dave Gosselin
MDEV-40620:  Name the kind of range a JOIN_TAB stands for

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.
Dave Gosselin
BUGFIX:  Read a FULL JOIN's left side in full when the join above keeps its NULLs

The pass that produces a FULL JOIN's right side rows emits a right row
only when no left row matched it, so the record of which right rows
matched is complete only once every left row has been read.  An
enclosing join's ON that reads a left side column could supply a key
value for that table, and the plan then read the left side on that key
instead of scanning it.  A plain equality hid the problem because it
rejects the NULLs the FULL JOIN pads its left side with.  A null safe
equality keeps those rows, and a right row whose match went unread came
back as a row that never matched.

Key access on such a table is now refused, both where the plan picks an
access path and where the ref is built from the chosen plan.  A key
value that reads only tables of the same left side is still allowed,
since it decides where one left side table is read from another rather
than how much of the left side the FULL JOIN sees.  Section 36 of
full_join.test covers the shape.
Dave Gosselin
MDEV-40620:  Add optimizer switches for the four join transformations

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.
Dave Gosselin
MDEV-XXXXX:  Keep an ON equality that a FULL JOIN left side reads by key

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.
Sergei Golubchik
disable DBUG in plugins, better deb version detection

dbug can be enabled with -UDBUG_OFF, if *really* needed
Dave Gosselin
MDEV-39746: FULL JOIN with a nested join on the right loses rows

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.