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
Oleksandr Byelkin
new columnstore
Fariha Shaikh
MDEV-39153 Fix sporadic main.change_master_default mismatch

The test used "restart_abort:" in the expect file, which MTR never
recognized. It fell through to the else branch, deleted restart_opts,
and started the server with defaults (heartbeat_period=60 instead of 0).

Replace restart_abort with direct --exec $MYSQLD calls.

All new code of the whole pull request, including one or several files
that are either new files or modified ones, are contributed under the
BSD-new license. I am contributing on behalf of my employer Amazon Web
Services, Inc.
Kristian Nielsen
Fix locking bug around rpl_binlog_state::find_most_recent().

The rpl_binlog_state::find_most_recent() returned an rpl_gtid* pointing into
internal memory of the rpl_binlog_state object that could change anytime
after returning from the function, potentially having the caller read
invalid data.

Fix by instead copying the rpl_gtid value out into caller-supplied memory.

Signed-off-by: Kristian Nielsen <[email protected]>
Yuchen Pei
MDEV-40805 Do not call lock_rec_convert_impl_to_expl if a table S-lock is held

lock_clust_rec_read_check_and_lock() skipped the implicit-to-explicit
conversion only under a table LOCK_X. When a table LOCK_S is held the
conversion is equally pointless: no other transaction can hold an
implicit X-lock on the record, because modifying a row requires a
table LOCK_IX and LOCK_IX is incompatible with our LOCK_S.
lock_table_has() matches stronger modes, so testing LOCK_S subsumes
the old LOCK_X test.
Oleksandr Byelkin
Merge branch '10.6' into 10.11
Rex Johnston
MDEV-40012 Parallel Query: execute the join in the worker threads

Each parallel worker now runs the join and the WHERE over its own chunk of the
driving table and ships the base-table columns of every row that qualified. The
manager copies those columns back into the fields they came from in its own
table instances, so that after a row is drained its records hold what a serial
scan would have left there, and then evaluates the select list against them and
sends the row. This replaces the model where the workers shipped raw source
records and the manager ran the join.

The transport carries columns rather than projected values because anything the
manager does with a row beyond sending it reads a record, and not all of those
reads go through Items that could be re-pointed: create_tmp_table() builds
Copy_field pairs holding raw Field pointers into the base tables.

make_join_readinfo()'s gate, can_run_query_in_workers(), chooses the worker-side
path for an inner select-project[-join] over a parallel-scannable driving table:
no tmp table, no LIMIT/SQL_CALC_FOUND_ROWS/procedure/aggregate, no outer join,
no semijoin strategy the worker's plain nested loop cannot honour, and every
non-driving table reached by eq_ref, ref or full scan. Every expression it hands
a worker has to deep-copy into something that shares no node with the original
and reads no table outside the join, or the copy would carry the manager's own
items onto a worker's tables. do_select() then runs run_worker_side_join();
anything ineligible runs serially.

Each worker opens a private copy of every non-const table, deep-clones and
field-rebinds the conditions and the shipped column list, rebuilds each ref,
scans its chunk, runs its own inner nested loop and ships the columns it read
for each full match. It runs under the session's query id, so an item holding a
value for the statement recomputes it rather than handing out the one it was
cloned with.

sql_parallel_execution.cc holds all of the above; sql_parallel_workers.cc keeps
the worker threads, the batch channel and their lifetime.

Tests: parallel_query_worker_side, parallel_query_join and parallel_query_clone
compare a parallel result set against a serial one; parallel_query and
parallel_query_oom moved to a plain SELECT and were re-recorded.
.claude/commits/MDEV-40012.md records what came from the 13.0 branch verbatim,
what was adapted to this tree, and what is new here.

This commit was prepared with Claude Code: it ported the work from the 13.0
branch onto this tree's refactored structures and renamed scan API, found and
fixed the two holes this tree's deeper Item copying opens -- an expression
reaching a table the worker does not have, and a statement-lifetime item cached
against query id 0 -- split the file, and ran the suites.
Alexander Barkov
MDEV-40009 SIGSEGV in Sql_path::from_text

Problem:
- The string passed to Sql_path::from_text() could be in
  various character sets, returned by Item::val_str_ascii(),
  which is not necessarily utf8mb3.
- While the code in Sql_path::from_text() was written in the way
  that "str" was considered to be in utf8mb3.
- As as result, cs->charset() in this line:
    auto len = cs->charlen(curr, end);
  could return a negative value and the whole loop got stuck.

Changes:
- Fixing Sys_var_path::from_item() to use val_str() instead of val_str_ascii(),
  to get the original value from "item", without any conversion.
- Moving the conversion code inside Sql_path::from_text().
Marko Mäkelä
fixup! 53ab6144004ffbd12e01a36820fececcdfdb49f7
Alexander Barkov
MDEV-40790 SELECT INTO row_type_of.field crashes the server

The server crashed on DBUG_ASSERT on a SELECT into:
- a `ROW TYPE OF table1` field variable
- a `ROW TYPE OF cursor1` field variable

Fix:

- Adding a class my_var_sp_row_field_by_name
- Adding a method sp_rcontext::set_variable_row_field_by_name()
- Fixing the DBUG_ASSERT
Alexander Barkov
MDEV-39563 Implement UPDATE ... RETURNING ... INTO

Adding support for UPDATE .. RETURNING .. INTO queries.

For example:

  UPDATE t1 SET a=10,b=20 RETURNING a,b INTO va,vb;
  UPDATE t1 SET a=10,b=20 RETURNING a,b INTO @a,@b;

Note, ANALYZE UPDATE .. RETURNING .. INTO queries work,
ignoring the INTO clause.

  ANALYZE UPDATE t1 SET a=10,b=20 RETURNING a,b INTO va,vb;

These types of queries:
- REPLACE .. RETURNING .. INTO
- DELETE .. RETURNING .. INTO
do not work - they return an error.
They will be implemented separately, when needed.
Marko Mäkelä
fixup! b5501deaccc06941352c9980361266e47c09c53f
Oleksandr Byelkin
Merge branch 'br-11.4-merge' into bb-11.8-release
Daniel Black
MDEV-40801 ppc64le ro_after_init isn't pagesize aligned

Align ro_after_init using MAXPAGESIZE instead of COMMONPAGESIZE.

COMMONPAGESIZE may be smaller than the actual page size supported by
the target ABI. This can leave ro_after_init sharing an OS page with
adjacent sections, causing mprotect() to change permissions on data
outside ro_after_init.

Use MAXPAGESIZE so the section boundaries are aligned to the maximum
page size required by the target linker/ABI.

This is particularly important on architectures such as ppc64le and
aarch64, where the runtime page size can differ from COMMONPAGESIZE.

Before:
  .data          0x...1b80000
  ro_after_init  0x...1c70000
  .bss          0x...1c72000

After:
  ro_after_init starts and ends on MAXPAGESIZE boundaries, ensuring
  mprotect() only affects pages belonging to ro_after_init.

Co-authored-by: ChatGPT GPT-5.6 Luna <[email protected]>
Alexander Barkov
MDEV-40639 Open SYS_REFCURSOR crash, if many cursors inside a function

Problem:

If:
- A routine A() opened a SYS_REFCURSOR with a function B() in the SELECT list
- The function B() also opened some SYS_REFCURSORs

Then reallocation of the cursor array THD::m_statement_cursors could happen
during the execution of B(), so all sp_cursor_array_element pointers inside
sp_instr_copen_by_ref::exec_core() of routine A() became invalid.

Fix:

Chaging the data type of sp_cursor_array:
- from Dynamic_array<sp_cursor_array_element>
- to Dynamic_array<sp_cursor_array_element*>

So now only reallocations of the array of cursor pointers happen,
while sp_cursor_array_element instances always stay on their originally
allocated memory positions.

Note:
sp_cursor_array_element instances are allocated using the standart C++ "new"
and deleted using the standard C++ "delete". Using a MEM_ROOT does not
seem to be relevant here.
Daniel Black
MDEV-40801 ppc64le ro_after_init isn't pagesize aligned

Align ro_after_init using MAXPAGESIZE instead of COMMONPAGESIZE.

COMMONPAGESIZE may be smaller than the actual page size supported by
the target ABI. This can leave ro_after_init sharing an OS page with
adjacent sections, causing mprotect() to change permissions on data
outside ro_after_init.

Use MAXPAGESIZE so the section boundaries are aligned to the maximum
page size required by the target linker/ABI.

This is particularly important on architectures such as ppc64le and
aarch64, where the runtime page size can differ from COMMONPAGESIZE.

Before:
  .data          0x...1b80000
  ro_after_init  0x...1c70000
  .bss          0x...1c72000

After:
  ro_after_init starts and ends on MAXPAGESIZE boundaries, ensuring
  mprotect() only affects pages belonging to ro_after_init.

Co-authored-by: ChatGPT GPT-5.6 Luna <[email protected]>
Rex Johnston
MDEV-40012 Parallel Query: execute the join in the worker threads

Each parallel worker now runs the join and the WHERE over its own chunk of the
driving table and ships the base-table columns of every row that qualified. The
manager copies those columns back into the fields they came from in its own
table instances, so that after a row is drained its records hold what a serial
scan would have left there, and then evaluates the select list against them and
sends the row. This replaces the model where the workers shipped raw source
records and the manager ran the join.

The transport carries columns rather than projected values because anything the
manager does with a row beyond sending it reads a record, and not all of those
reads go through Items that could be re-pointed: create_tmp_table() builds
Copy_field pairs holding raw Field pointers into the base tables.

make_join_readinfo()'s gate, can_run_query_in_workers(), chooses the worker-side
path for an inner select-project[-join] over a parallel-scannable driving table:
no tmp table, no LIMIT/SQL_CALC_FOUND_ROWS/procedure/aggregate, no outer join,
no semijoin strategy the worker's plain nested loop cannot honour, and every
non-driving table reached by eq_ref, ref or full scan. Every expression it hands
a worker has to deep-copy into something that shares no node with the original
and reads no table outside the join, or the copy would carry the manager's own
items onto a worker's tables. do_select() then runs run_worker_side_join();
anything ineligible runs serially.

Each worker opens a private copy of every non-const table, deep-clones and
field-rebinds the conditions and the shipped column list, rebuilds each ref,
scans its chunk, runs its own inner nested loop and ships the columns it read
for each full match. It runs under the session's query id, so an item holding a
value for the statement recomputes it rather than handing out the one it was
cloned with.

sql_parallel_execution.cc holds all of the above; sql_parallel_workers.cc keeps
the worker threads, the batch channel and their lifetime.

Tests: parallel_query_worker_side, parallel_query_join and parallel_query_clone
compare a parallel result set against a serial one; parallel_query and
parallel_query_oom moved to a plain SELECT and were re-recorded.
.claude/commits/MDEV-40012.md records what came from the 13.0 branch verbatim,
what was adapted to this tree, and what is new here.

This commit was prepared with Claude Code: it ported the work from the 13.0
branch onto this tree's refactored structures and renamed scan API, found and
fixed the two holes this tree's deeper Item copying opens -- an expression
reaching a table the worker does not have, and a statement-lifetime item cached
against query id 0 -- split the file, and ran the suites.
Daniel Black
MDEV-40750 gcc-16.1.0 on ppc64 causes innodb to fail to compile

Assembler comes up with the error:
unrecognized opcode: `dcbstps'

dcbstps is a Power 10 instruction. The default target arch on most
platforms is Power 8 or 9.

Added the target power10 to the function pmem_phwsync. The execution
of this function is gated on the ISA 3.1 in pmem_persist_init so
there's no chance of a SIGILL.

clang supports this target as arch=pwr10 and gcc as cpu=power10.
Revert back to using opcodes for old versions.
bsrikanth-mariadb
MDEV-39226: Push whole multi-table update/delete down into engines

Give storage engines a way to take over an entire multi-table
UPDATE/DELETE, the way they can already take over a SELECT. Without it the
join, the row matching and every modification run in the SQL layer even
when an engine could do the whole statement itself in one step; a
single-table UPDATE/DELETE already avoids this via
direct_update_rows()/direct_delete_rows(), but a multi-table statement has
no primary handler object to drive that path.

This adds a generic, engine-agnostic pushdown interface: the SQL layer
offers the statement to the engine, and if the engine accepts it, it
performs the whole thing and reports only the row counts.

- Split select_handler into a pushdown_handler base with select_handler
  (result set) and a new multi_upddel_handler (runs a whole UPDATE/DELETE,
  reports row counts, reported as PUSHED UPDATE/PUSHED DELETE); add
  handlerton::create_multi_upddel, looked up in Sql_cmd_dml::execute_inner().
- multi_update/multi_delete gain direct_update_delete_done(), which records
  the engine's counts so send_eof() binlogs and replies without the
  SQL-layer loop; it forces statement-format binlogging so the change still
  replicates under binlog_format=ROW, and errors out instead of silently
  dropping counts for an unsupported result object.
- FederatedX implements the interface as the reference engine used to test
  correctness: it prints the statement back and runs it remotely, passes
  the engine's error code/SQLSTATE through, reads the matched count from the
  remote info string, executes IGNORE locally, and only pushes down when all
  tables share one remote server (same as SELECT/derived/unit pushdown).

Test: federated.federatedx_pushdown_upd_del.
Marko Mäkelä
MDEV-40728 Recovery wrongly fails if FILE_CREATE is followed by FILE_RENAME

fil_name_process(): Simplify the logic. If no matching tablespace is
found but file_name_t::create_lsn had been set in response to parsing
a FILE_CREATE record, try to apply FILE_RENAME to deferred_spaces.

deferred_spaces.deferred_dblwr(): Skip newly created tablespaces
to avoid a bogus invocation of fil_space_free().

fil_delete_apply(): Wrappers for fil_space_free(). When recovering
a log in innodb_log_archive=ON format, we must apply FILE_DELETE
records in order to avoid a future clash with FILE_CREATE or FILE_RENAME.
Rex Johnston
MDEV-40012 PQ: divide the whole join's cost, not the split table's

scale_cost_for_parallel_scan() discounted the driving table's scan
and nothing else, so every table joined after it was costed at its
ull serial price. But a worker does not only scan its chunk: it runs
the whole join over that chunk, so the work of each later table is
divided between the workers exactly as the scan is.

POSITION gains parallel_workers, set on the driving table when the
access finally chosen for it was the scan that was costed as parallel,
and left at 0 otherwise, including on a driving table whose scan lost
to an index, where nothing is parallel. Each table joined after it
divides its cost by that number.

This commit was prepared with Claude Code
Fariha Shaikh
MDEV-39459 Fix bad sync pattern for chain replication MTR tests

In chain replication (1->2->3), syncing only server_3 after
save_master_gtid on server_1 does not guarantee server_2 has committed,
because server_2's binlog dump thread can send events to server_3 before
commit_ordered() completes on server_2.

Fix affected rpl tests by syncing server_2 before server_3, and update
result files accordingly.

All new code of the whole pull request, including one or several files
that are either new files or modified ones, are contributed under the
BSD-new license. I am contributing on behalf of my employer Amazon Web
Services, Inc.
Oleksandr Byelkin
Merge branch 'bb-11.8-release' into bb-12.3-release
Arcadiy Ivanov
MDEV-40802 COUNT(DISTINCT <blob>) fails when its tmp table converts

`COUNT(DISTINCT)` collects the distinct values in a temporary table
with a unique constraint over the aggregate's arguments, and treats a
duplicate key error from the write as "value already seen":

```c
if (!table->file->is_fatal_error(error, HA_CHECK_DUP))
  return FALSE;                          // duplicate, not an error
```

For a blob argument the record holds only a pointer to the value, so
`Aggregator_distinct::setup()` cannot use the `Unique` tree, which
compares raw record bytes, and every value goes through that write
instead.

When such a write overflows the in-memory table,
`create_internal_tmp_table_from_heap()` copies the stored rows to an
on-disk table and then writes the row that overflowed, which until
then was held in `record[0]` alone. Whether a duplicate key error on
that last write is fatal is decided by the caller's
`ignore_last_dupp_key_error` argument, and `Aggregator_distinct::add()`
passed **0** three lines below the code that ignores the very same
condition. The statement failed with

    ERROR 1169 (23000): Can't write, because of unique constraint,
    to table '(temporary)'

Pass **1** instead, so that a duplicate arriving through the conversion
is discarded exactly like one arriving through the ordinary write. The
result is `table->file->stats.records` of that table, so not storing
the duplicate is what makes the count right.

The argument is the same upstream, where it is unreachable: a
temporary table with a blob column was created on the on-disk engine
to begin with, so the conversion was never entered for the only
tables whose pending row can be a duplicate. Supporting blob columns
in the in-memory engine made the table start in memory and convert.

New tests `heap.count_distinct_blob_convert` and
`heap.count_distinct_blob_convert_debug`.

A write rejected as a duplicate returns its record to the free list and
never reaches the allocation of the blob value, so only the first copy
of a value makes the in-memory table grow, and the write that finds it
full is the second copy of the value stored last. That holds only while
a record slot is what the table runs out of first. Blob values come out
of the same space, and only a write that is not a duplicate ever
allocates one, so when a blob allocation is the one that hits the limit,
the pending row is not a duplicate at all.

Which of the two runs out first follows from how records and blob values
pack together, not from any threshold on the value width. Of 24 measured
combinations of width and `max_heap_table_size`, 20 convert but only 8
reach a duplicate pending row, so asserting that the table was converted
does not establish that the ignored duplicate was reached.

The first test uses widths measured to overflow on a record slot. The
second removes the dependency on that measurement, injecting the
duplicate through a new debug point in
`Tmp_table_default_copier::copy_rows()`, beside the one the row copy
loop already carries. Every value is present twice, so whichever copy
the injected duplicate discards, the other one is still written and the
count does not depend on which write overflowed.

The status counter is read with the in-memory limit restored. The status
table is materialized into a temporary table of its own, and its
VARIABLE_VALUE column is wide enough to be stored as a blob, so under
the shrunken limit that table can overflow and be converted as well, and
would then report its own conversion.
Oleksandr Byelkin
MDEV-40173 RPM conflicts on /usr/lib64/security

The move of the install location of pam files in MDEV-37197
(34aac090f2acc1a4b5850810fe41370c19659d55) resulted in different
install locations on different RPM distros.

Correct the RPM packaging to ignore the path of the pam files
(but not the pam files themselves).
Marko Mäkelä
MDEV-40728 Recovery wrongly fails if FILE_CREATE is followed by FILE_RENAME

fil_name_process(): Simplify the logic. If no matching tablespace is
found but file_name_t::create_lsn had been set in response to parsing
a FILE_CREATE record, try to apply FILE_RENAME to deferred_spaces.

deferred_spaces.deferred_dblwr(): Skip newly created tablespaces
to avoid a bogus invocation of fil_space_free().

fil_delete_apply(): Wrappers for fil_space_free(). When recovering
a log in innodb_log_archive=ON format, we must apply FILE_DELETE
records in order to avoid a future clash with FILE_CREATE or FILE_RENAME.
Marko Mäkelä
Explicit copy_file() shortcuts

copy_file_range_try(): A wrapper for Linux copy_file_range(2),
which may fail with EOPNOTSUPP or EXDEV and require a fallback.

copy_mmap(): Copy from a memory-mapped buffer.

copy_file(): The generic file-copying function. On FreeBSD and
Microsoft Windows, no alternatives exist.

InnoDB_backup::backup(): Implement all alternatives to copy_file().
Andrei Elkin
MDEV-40824 slave_run_triggers_for_rbr enabled slave ignores after-insert trigger's error

MDEV-15990 commit did not handle after-trigger error on
slave_run_triggers_for_rbr slave server.
That made an error from such failing trigger lost.

The reason was a flaw in new logics coded for
Write_rows_log_event::write_row().

It is amended now. For any sql error out of the after-trigger
HA_ERR_GENERIC handler error code is returned instead of zero. That
error-stops serial slave as expected (also by pre-MDEV-15990 code).
Optimistic parallel slave may retry, contingent upon the trigger's sql error.

Note: idempotent-mode tolerance of trigger-internal errors and
the per-row last_errno reset are left for a follow-up.
KhaledR57
MDEV-40494 KEY_OP_DEL_PREFIX subtracts unchecked length from page_length

The KEY_OP_DEL_PREFIX branch of _ma_apply_redo_index() subtracted the
logged length from page_length and used the difference as a bmove() size,
guarded only by a DBUG_ASSERT, which is compiled out when DBUG_OFF is set.
A length larger than the used page underflows that size, and a
length larger than page_length wraps page_length itself.

Turn the assert into a runtime check, written as an addition rather than
the assert's subtraction, which underflows on a page shorter than its own
header. A refused record takes the error path that was already there.

The test forges the logged length with corrupt_del_prefix, then crashes the
server so recovery has to replay the record. The workload splits and merges
index pages: this record comes from the underflow path, so plain inserts
never produce one.
Oleksandr Byelkin
Merge branch '11.4' into 11.8
Georgi (Joro) Kodinov
MDEV-40661: mysql_upgrade.test not stable on a busy server

The cleanup of the old connection goes on in the background.
It can take longer on a busy server and this triggers the active sessions
warning in DROP user.
Stablizied the test by disabling the warnings.
Arcadiy Ivanov
MDEV-40781 Duplicate row despite DISTINCT when tmp table converts

`create_internal_tmp_table_from_heap()` writes the pending `record[0]`,
the row whose write filled the in-memory table, into the new table.
Since **MDEV-40376** (`636f154bb49`) that write happens *before*
`ha_end_bulk_insert()` rather than after it.

`ha_maria::start_bulk_insert()` disables **all** indexes of an internal
temporary table that is about to receive at least
`MARIA_MIN_ROWS_TO_DISABLE_INDEXES` (100) rows:

```c
if (file->open_flags & HA_OPEN_INTERNAL_TABLE)
{
  /* Internal table; If we get a duplicate something is very wrong */
  file->update|= HA_STATE_CHANGED;
  index_disabled= share->base.keys > 0;
  maria_clear_all_keys_active(file->s->state.key_map);
}
```

`maria_write()` then skips `_ma_check_unique()` entirely, so the unique
constraint that implements `DISTINCT` for a key too wide to be an index
is not enforced. The rows copied out of the in-memory table are already
distinct and need no checking against each other, but the pending row is
exactly the row whose duplicate status is unknown, and it was written
inside that window. A `SELECT DISTINCT` over wide columns could
therefore return a duplicate row.

Note that the justification given in `636f154bb49` is not the mechanism
at work here. It refers to the bulk insert key *tree*, a different
branch of `ha_maria::start_bulk_insert()`; setting
`bulk_insert_buffer_size=0` does not avoid the problem.

The fix splits the copy in two:

1. `Tmp_table_row_copier` gains a second virtual,
  `write_pending_row()`, defaulting to a no-op.
2. `copy_rows()` now only copies the rows the in-memory table holds.
3. `create_internal_tmp_table_from_heap()` calls
  `ha_end_bulk_insert()` and then `write_pending_row()`, so the
  pending row is written with the indexes of the new table back in
  place and a duplicate of an already copied row is detected.

`Window_rowid_remapper` keeps writing its pending row within
`copy_rows()` and inherits the no-op default. Its new position is only
known once the rows before it have been written, and nothing is lost by
writing it with the indexes still disabled: it replaces a row that is
already in the table rather than adding one, and an update of a window
function value cannot collide with another row, as a deduplicating key
is not built on the columns it changes.

The new test covers `SELECT DISTINCT`, `SELECT DISTINCT ... ORDER BY`,
`UNION` and `INSERT ... SELECT DISTINCT`, and asserts that the
conversion actually happened so that a future sizing change cannot
silently void the coverage.
Kristian Nielsen
MDEV-40729: Refactor my_xid to be a struct with the full data

This is a refactoring commit in preparation for changing the internal XID
used for binlog two-phase commit and recovery.

The existing internal XID consists of server_id (4 bytes) and query_id (8
bytes). But the existing code only puts the query_id into the my_xid
datatype, leaving the server_id implicit in the parts of the code that pass
around my_xid, thus hard-wiring to the server_id global value.

This commit changes my_xid to be a struct containing conn_id and commit_id,
to match what will be used in MDEV-40729 where these constitute a
transaction id shared between the server and the client.

To clearly distinguish the refactor-only changes from changes in logic, this
commit only changes the code to pass around two-value my_xid explicitly, but
preserves the existing (server_id, query_id) values in internal XID. It maps
server_id to commit_id, and query_id to conn_id.

An empty / dummy my_xid is used with the value 0 in a couple of places in
the existing code; this is replaced with a my_xid with commit_id==0. This
will not conflict with real XID, as server_id cannot be 0, and in MDEV-40729
the commit_id will start at 1.

Signed-off-by: Kristian Nielsen <[email protected]>
Oleksandr Byelkin
Merge branch '11.4' into br-11.4-merge
Prathamesh Hukkeri
MDEV-40122: `+DEFAULT` is not a valid value for master_heartbeat_period

MDEV-28302 changed the grammar for master_heartbeat_period to accept
DEFAULT (via num_or_default), while MDEV-38454 added an opt_plus to
allow numeric values with an explicit `+` sign.  The combination made
the rule `opt_plus num_or_default`, which also accepted `+DEFAULT`,
equivalent to `= DEFAULT`.

Move the opt_plus under num_or_default's definition, so that `+` may
only precede a numeric literal, and DEFAULT is a separate alternative.
Now `master_heartbeat_period= +DEFAULT` is a syntax error again, while
`= +45` and `= DEFAULT` are both accepted.
Rex Johnston
MDEV-40012 PQ: divide the whole join's cost, not the split table's

scale_cost_for_parallel_scan() discounted the driving table's scan
and nothing else, so every table joined after it was costed at its
ull serial price. But a worker does not only scan its chunk: it runs
the whole join over that chunk, so the work of each later table is
divided between the workers exactly as the scan is.

POSITION gains parallel_workers, set on the driving table when the
access finally chosen for it was the scan that was costed as parallel,
and left at 0 otherwise, including on a driving table whose scan lost
to an index, where nothing is parallel. Each table joined after it
divides its cost by that number.

This commit was prepared with Claude Code
Sergei Petrunia
MDEV-40738: main.cte_update_delete missing DROP VIEW v1, refers to wrong MDEVs.

Fix the testcase.
Oleksandr Byelkin
Merge branch '10.11' into 11.4
bsrikanth-mariadb
MDEV-39226: Push whole multi-table update/delete down into engines

Give storage engines a way to take over an entire multi-table
UPDATE/DELETE, the way they can already take over a SELECT. Without it the
join, the row matching and every modification run in the SQL layer even
when an engine could do the whole statement itself in one step; a
single-table UPDATE/DELETE already avoids this via
direct_update_rows()/direct_delete_rows(), but a multi-table statement has
no primary handler object to drive that path.

This adds a generic, engine-agnostic pushdown interface: the SQL layer
offers the statement to the engine, and if the engine accepts it, it
performs the whole thing and reports only the row counts.

- Split select_handler into a pushdown_handler base with select_handler
  (result set) and a new multi_upddel_handler (runs a whole UPDATE/DELETE,
  reports row counts, reported as PUSHED UPDATE/PUSHED DELETE); add
  handlerton::create_multi_upddel, looked up in Sql_cmd_dml::execute_inner().
- multi_update/multi_delete gain direct_update_delete_done(), which records
  the engine's counts so send_eof() binlogs and replies without the
  SQL-layer loop; it forces statement-format binlogging so the change still
  replicates under binlog_format=ROW, and errors out instead of silently
  dropping counts for an unsupported result object.
- FederatedX implements the interface as the reference engine used to test
  correctness: it prints the statement back and runs it remotely, passes
  the engine's error code/SQLSTATE through, reads the matched count from the
  remote info string, executes IGNORE locally, and only pushes down when all
  tables share one remote server (same as SELECT/derived/unit pushdown).

Test: federated.federatedx_pushdown_upd_del.
Oleksandr Byelkin
Merge branch 'br-10.11-merge' into bb-11.4-release