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
Rex Johnston
MDEV-39492 Parallel Query: a re-split chunk lost its first sub-tree

Parallel_reader::Ctx::split() divides one chunk into smaller ones by calling
Scan_ctx::partition(), which descends from the index root again bounded by that
chunk's start key. It is the first caller in the engine to pass a start bound at
all: while the SQL layer asked only for whole-table scans, every partition began
before the first record and the bound was always null.

Scan_ctx::create_ranges() searched for that bound with PAGE_CUR_GE at every
level. Above the leaves a record is a node pointer keyed by the first key of the
child it points at, so the child that contains a start key is the last one whose
key is not greater than it. GE lands on the child after that one, so the
sub-tree holding the start key was never descended into, no range was created
for it, and every row in it was left out of the scan with nothing reported
anywhere. Scan_ctx::search() already makes the distinction -- PAGE_CUR_LE above
the leaves, PAGE_CUR_GE at them -- and the comment at the top of the file
describes it. create_ranges() now makes it too.

A chunk is only re-split once the tree is at least three levels deep, which no
existing test reached: they are all a few thousand narrow rows, and a table of
INTs needs some 700000 of them for a third level. The new test gets there in
5000 rows by giving the table a wide PRIMARY KEY, which shrinks the node pointer
fanout. Without the fix it answers 2591 of those 5000 rows in the workers and
5000 serially.

main.parallel_query_worker_count did have a table deep enough to be re-split,
t4, and its check that every row is read exactly once was written as a bare
aggregate -- a shape the parallel gate refuses -- so it ran serially and compared
the serial answer with itself. That scan was in fact returning 3492 of t4's 6000
rows. The check now reduces the rows outside a non-merged derived table, so what
it measures is a scan the workers ran, and it fails without this fix. t4's chunk
count per query rises from 10 to 18, which is the sub-trees a re-split no longer
skips.

This commit was prepared with Claude Code: the row loss turned up while it was
benchmarking an unrelated change, and it bisected the cause to the re-split path
by disabling re-split, identified the GE/LE descent, wrote the fix, found the
wide-PRIMARY-KEY shape that reproduces it in a table small enough for a test,
noticed that the existing completeness check was hollow, and verified that both
tests fail with the fix reverted.
Yuchen Pei
MDEV-40467 [to-squash] Improve Y2038/2106 problem error message

When nearing the end times TIMESTAMP_MAX_VALUE, auto-partition
creation may encounter overflow when calculating new partition range.
This patch improves the message of such an overflow
Oleksandr Byelkin
new CC 3.3
Rex Johnston
MDEV-39492 Parallel Query: run LIMIT and OFFSET in the workers

The gate refused a query with LIMIT or OFFSET. It no longer needs to: since the
manager's loop ends in end_send(), the limit is applied by the server, on the
manager's single thread, and select_result_sink::send_data_with_check() applies
the offset in the same place. Reaching the limit returns NESTED_LOOP_QUERY_LIMIT,
which manager_collect_and_send() turns into a stop for the producers -- the flag
the batch handoff protocol has carried since it was written and never used. So the
number of rows delivered is exact and the scan stops early rather than draining.

Which rows arrive is another matter, and that is the reason this was left for a
decision rather than committed with the refactor. A parallel scan delivers
whichever rows the workers finish first, not the ones a serial scan would reach.
For a query with no ORDER BY there is no order for a prefix to be taken of, so
both are correct answers to the question asked; the ordered case is refused by
the gate and runs serially, unchanged. Depending on the rows an unordered LIMIT
happens to return is an antipattern, and not one this server is going to keep
supporting on the strength of a test that encodes it.

In the event nothing in the suite encoded it. The main suite with
parallel_worker_threads forced on is at the same eighteen failures as before, all
EXPLAIN and cost output, and normal runs are untouched because
parallel_worker_threads is 0 by default. The reason is worth recording: nearly
every table in the suite is small enough that the engine cannot divide it, and
"decline by the chunk count, not the worker count" refuses those outright, so
their LIMIT results were never going to move.

parallel_query_limit compares how many rows six limit and offset shapes deliver
serially and in the workers, counted through a non-merged derived table so the
figure is the query's row count. FOUND_ROWS() is not used for this: it counts
rows the executor processed, so with OFFSET it includes the skipped ones -- 8 for
LIMIT 5 OFFSET 3 -- and serial does the same, so it is consistent but not the
number the test is about. Content is asserted only where a single row can
qualify, and the ORDER BY case asserts that it did not run in the workers, so the
gate's remaining refusal cannot lapse unnoticed.

This commit was prepared with Claude Code: it verified the limit path against a
temporarily opened gate before proposing the change, established that FOUND_ROWS()
counts processed rather than delivered rows by comparing serial with parallel
rather than assuming a bug, and confirmed no test in the suite depends on the
order of an unordered prefix.
Sergei Golubchik
MDEV-40571 insufficient validation of frm data when opening a table

numerous checks that the frm is valid, no OOB reads,
values make sense (number of keyparts not less than number of keys,
no keys means no keyparts, number of long unique fields is not larger than
number of fields, fields values in the record don't overlap and don't
go over record ends, and so on). most asserts were changed to if()'s
forkfun
MDEV-39566 fix status_by_thread crash on live thread-count change

PFS_table_context snapshots the live thread/user/host/account
count at scan start and again on restore (filesort's second
rnd_init). If the count changed between the two, m_map_size
mismatched and the server aborted.

Skip the wasted re-sample on restore, bound each table's scan by
the frozen snapshot instead of the container's live count.
Rex Johnston
MDEV-39492 Parallel Query: give a worker its own JOIN

No functional change: nothing reads the worker's JOIN until its JOIN_TABs are
driven through sub_select(). Second step of replacing the worker's private nested
loop with the executor's.

A worker cannot share the manager's JOIN. sub_select() writes join->return_tab as
it descends -- the level to unwind to, set unconditionally on entry -- and the
guard that reads it compares against JOIN_TAB*, so with a JOIN_TAB array per
worker one worker's backtrack point would be compared against another's tabs:
pointers into different allocations, and a scan that can end early for no
visible reason. The stronger reason is join->thd, read at twelve sites in that
loop for the diagnostics area, the killed flag and the row counters. Sharing the
JOIN would send all of it to the manager's THD from a worker thread, which is the
bug fixed in "a worker's result table belongs to the worker", reintroduced once
per row and for every query shape. Unlike return_tab it cannot be gated away.

JOIN declares its copy constructor and assignment private and unimplemented, so
the worker's is built by JOIN's own constructor and given what it needs by name.
That constructor is JOIN::init(), a field-initialiser that allocates nothing,
plus a shallow copy of fields_list, so it is cheaper as well as better defined
than copying the bytes of a class whose author said not to. It also matches how
Join_plan_state already holds part of a JOIN: the convention here is a named list
of fields, not an object copy.

Only three fields need carrying, which is what makes a named list practical.
Walking sub_select() and evaluate_join_record() for the shapes the gate allows
leaves thd, return_tab and found_records. map2table is read only for
split_derived_to_update; join_tab_execution_startup() reads join only inside its
two semijoin-materialization branches; JOIN_TAB::preread_init() returns before
touching join->thd unless the table is a materialized derived. All three are
already asserted inert per table by pwt_assert_tab_inert().

result is left null deliberately. It is the manager's connection to the client,
and manager_collect_and_send() is the only thing that may send a row, so a worker
that reaches for it crashes rather than writing to a socket two threads share.

pwt_assert_join_inert() is the same idea as pwt_assert_tab_inert() one level up,
and none of its checks fires across the main suite with parallel_worker_threads
forced on. The sizeof(JOIN) tripwire is debug-only on purpose: JOIN carries
dbug_join_tab_array_size under #ifndef DBUG_OFF, so its size is not the same in
the two build types, and pinning both would leave two magic numbers of which only
one is ever checked by whoever changes the class.

This commit was prepared with Claude Code: it established the three-field surface
by walking the executor and the helpers it calls, found that JOIN is
deliberately non-copyable after having earlier reported the opposite, and
confirmed that main.xtradb_mrr appearing in one forced-worker sweep is a
load-dependent warning-count flake rather than a regression -- it passed six of
seven runs in isolation and a second sweep is back at the known eighteen.
Yuchen Pei
MDEV-15621 Auto add RANGE COLUMNS partitions by interval

Allow auto partitioning by interval in PARTITION BY RANGE COLUMNS

PARTITION BY RANGE COLUMNS (col_name)
INTERVAL interval [AUTO]
(
  PARTITION partition_name VALUES LESS THAN (value)
  [, PARTITION partition_name VALUES LESS THAN (value) ... ]
)

where

- col_name is the name of one column of type DATE or DATETIME or
  TIMESTAMP

- at least one partition is supplied, and the highest partition cannot
  have MAXVALUE range

- INTERVAL interval is a positive time interval. it can be mariadb
  format or oracle NUMTODSINTERVAL/NUMTOYMINTERVAL format. Like
  versioning, the smallest unit is second, i.e. no subsecond like
  microsecond.

- DATE column cannot have interval with values less than a day

- Subpartitions are allowed, but restricted to existing subpartition
  types, i.e. [LINEAR] (KEY|HASH)

When performing one of the following DML statements on such a table,
it will first add partitions by the specified interval until the
partition covers the current time:

- INSERT
- INSERT ... SELECT
- LOAD
- UPDATE
- REPLACE
- REPLACE ... SELECT

Partition addition will not cause an implicit commit like DDL normally
does.

The partitions are named pN.

Otherwise the table behaves exactly the same as a normal RANGE COLUMNS
partitioned table.

Note that TIMESTAMP is not allowed as a type for PARTITION BY RANGE
COLUMNS otherwise.

Including the following fixes:

1.

MDEV-40088 Disallow subqueries in INTERVAL clause in range interval auto partitioning

This is consistent with system time (versioning) partitioning. Also
consistent is that both allow expressions otherwise.

2.

MDEV-40048 Allow trigger and LOCK TABLES to work with range interval auto partitioning

When a range interval auto partitioned table is the target of a
trigger, the triggering statement is not necessarily one that would
cause the auto-creation of new partitions, so we need to account for
that.

Also added support for LOCK TABLES ... WRITE.

Improved tests coverage by adapting tests from versioning.partition.

3.

MDEV-40467 Improve Y2038/2106 problem error message

When nearing the end times TIMESTAMP_MAX_VALUE, auto-partition
creation may encounter overflow when calculating new partition range.
This patch improves the message of such an overflow
Sergei Golubchik
Revert "MDEV-39622 OBJECT_INSTANCE_BEGIN in P_S are unstable, difficult to compare"

Let's use MySQL's fix for compatibility

This reverts commit 11c41cd93d2c3732862ba043afd18508021440c0.
Rex Johnston
MDEV-39492 Parallel Query: COUNT and SUM with no GROUP BY

The workers do not aggregate. They run the select-project query over their chunk
and ship the aggregates' arguments, one row per qualifying row, and the manager
accumulates them with the query's own Item_sum objects by calling the server's
end_send_group() -- the function a serial plan of this shape already uses. For
the length of the drain each aggregate's arguments()[0] points at the field of
result_table its value arrived in, and is put back afterwards, because the query
belongs to the statement and may run again and run serially the next time.

So no Item_sum is copied into a worker, and that is the point. Computing partial
aggregates in the workers needs a clone per worker, and Item_sum's deep_copy()
reaches the implicit copy constructor: the clone shares the Aggregator that
Item_sum::cleanup() deletes. It also needs a combine rule per kind, since a
manager that aggregated the partials with the query's own COUNT would count the
partials. Neither arises here. The manager sees every row that qualified, exactly
as it would have read them itself, and an empty result still answers COUNT 0 and
SUM NULL through end_send_group()'s own end-of-records path rather than through a
special case.

Only COUNT and SUM are accepted, because the manager's mechanism is to redirect
arguments()[0] and those are the kinds that read their argument from there each
time they are added to. Item_sum_min_max reads it through an Item_cache built
when the aggregate was fixed, so a redirected argument never reaches it and MIN
answered NULL where it should have answered a value; AVG, STD and VARIANCE are
left out until each is checked against the same question, and the DISTINCT
variants hold the set in an Aggregator keyed to the original argument. The gate
refuses those kinds, and also a select-list item that merely contains an
aggregate, so all of them run serially.

The list end_send_group() sends is join->fields, which is not always
&join->fields_list even for these plans: with no WHERE clause the planner leaves
it at &select_lex->item_list, the same items reached through another list header.
The redirect walks the list that will be sent, and checks as it goes that
position i of it holds the item the gate classified at position i, which is what
the column mapping is indexed by.

Measured on a table of 1000000 rows with four workers, a debug build, median of
five runs: COUNT(*) 3.0 times faster than serial, SUM(val) 3.2, and 4.0 for a SUM
under a selective predicate. Shipping every qualifying row in order to count it
costs less than the scan being parallelised, which is the opposite of what was
expected when this route was first weighed against per-worker partials.

This commit was prepared with Claude Code: it wrote the transport and the
manager-side redirect, established by measurement which aggregate kinds the
redirect actually reaches (MIN answering NULL is what narrowed the set to COUNT
and SUM), wrote the test, and confirmed the test catches removal of the redirect,
a one-column shift in the mapping, and end_send() substituted for
end_send_group().
Yuchen Pei
MDEV-40088 [to-squash] Disallow subqueries in INTERVAL clause in range interval auto partitioning

This is consistent with system time (versioning) partitioning. Also
consistent is that both allow expressions otherwise.
Alexey Botchkov
MDEV-39750 ExtractValue does not control recursion depth.

Stack exhaustive test shouldn't be ran with the ASAN/UBSAN.
Rex Johnston
MDEV-39492 Parallel Query: ship base-table columns, not projected values

A worker used to run the whole select-project query over its chunk and ship the
projected select list. It now ships the base-table columns the query reads, in
table order, and the manager copies each one back into the field it came from in
its own table instances. Then, instead of the manager choosing a terminal
function itself, it hands each row to the plan's own: the last real table's
next_select, called exactly as sub_select() would call it.

The result is that everything downstream of the driving scan reads what a serial
scan would have left in those records, with nothing redirected. That deletes
three mechanisms rather than adding one. The reference-array slice built over the
result table is gone, and HAVING resolves through the records instead. The
per-item projection list the manager sent is gone, and the select list is
evaluated by end_send() as usual. The redirect that pointed each aggregate's
arguments()[0] at a shipped column is gone, and the aggregates read their own
arguments.

Carrying columns rather than values is what the next stage needs, not merely
tidier. An aggregation temp table is filled by copy_fields(), which walks
Copy_field pairs holding raw Field pointers into the base tables, so a temp-table
stage cannot be satisfied by re-pointing Items -- the values have to be in the
base-table records. Nothing in this commit has such a stage, since the gate still
refuses one, but this is the change that makes GROUP BY reachable.

Every kind of aggregate now runs in the workers, where before only COUNT and SUM
did, and the restriction that produced that list is gone with the redirect it
described. MIN and MAX read their argument through an Item_cache built when they
were fixed, AVG and STD keep their own counters, and the DISTINCT variants keep
the set in an Aggregator; none of that is the transport's business once the record
is right. HAVING beside an aggregate works for the same reason. GROUP_CONCAT is
refused, and now for the only reason left: its value is the order the rows arrived
in, and workers finish chunks in an order that varies between runs, so it would
answer differently each time.

Two gate checks went with the design they served. The select list is no longer
tested for clonability, because nothing clones it -- and probing it was actively
harmful, since pwt_item_is_clonable() copies an item to test it and copying an
Item_sum_min_max crashes: its copy constructor leaves cmp uninitialised while its
cleanup() deletes it. The refusal of a select list with hidden items is gone too;
it existed so that reference-array slot i and result-table column i lined up, and
neither is used now.

This commit was prepared with Claude Code: it identified that copy_fields() holds
raw Field pointers and so decided the transport shape, wrote the column shipping
and the copy-back, replaced the hardcoded terminals with the plan's own,
established by test that the aggregate-kind restriction was then unnecessary,
found that the gate's clonability probe was what crashed on MIN, and updated the
tests whose recorded expectations the wider capability changed.
Sergei Petrunia
Remove incorrectly added sql/opt_sum.cc.orig
Georgi (Joro) Kodinov
Updated README.md to contain a more friendly version.
Yuchen Pei
MDEV-40048 [to-squash] Allow trigger and LOCK TABLES to work with range interval auto partitioning

When a range interval auto partitioned table is the target of a
trigger, the triggering statement is not necessarily one that would
cause the auto-creation of new partitions, so we need to account for
that.

Also added support for LOCK TABLES ... WRITE.

Improved tests coverage by adapting tests from versioning.partition.
Teemu Ollakka
Galera MTR: disable SSL by default for SST, guard tests missing stunnel

Set ssl-mode=DISABLED in [sst] across all top-level galera/galera_3nodes
topology .cnf files, so tests no longer implicitly require stunnel via
inherited ssl-ca/ssl-cert/ssl-key. Also add the missing have_stunnel.inc
skip guard to galera_ist_rsync_verify_ca and galera_sst_cn_injection's
rsync combination, which intentionally test SSL SST but were not
skipped when stunnel is absent.
Teemu Ollakka
wsrep_info MTR: disable SSL by default for SST
Georgi (Joro) Kodinov
MDEV-39718: Produce Markdown plugin API documentation

Generated the plugin API headers using a shell script.
Fixed some doxygen comment mistakes in the headers.
Added a cmake conveninence target to generate the docs into $BUILD_DIR/docs
Added a main page for the API docs.
Included all of the existing group .md files into the CMake target
Leveraged moxygen 2.1.11's fixes to produce the full API docs in a single go
Removed the list of output .md files from the CMake target and switched to a
stamp file to avoid unnecessary rebuilds of the docs when the list of .md
files changes.
Addressed various review comments.
Thirunarayanan Balathandayuthapani
MDEV-19574: innodb_stats_method is not honored when innodb_stats_persistent=ON

Problem:
=======
When persistent statistics are enabled (innodb_stats_persistent=ON),
the innodb_stats_method setting is not properly utilized during
statistics calculation.

The statistics collection functions always use a hardcoded default
behavior for NULL value comparison instead of respecting the
configured stats method (NULLS_EQUAL, NULLS_UNEQUAL, or
NULLS_IGNORED). This affects the accuracy of n_diff_key_vals
(distinct key count) and n_non_null_key_val estimates, particularly
for indexes with nullable columns containing NULL values. This
impacts the query optimizer, which makes decisions based on
inaccurate cardinality estimates.

Solution:
========
Introduced IndexLevelStats to collect statistics at a specific
B-tree level during index analysis.

Introduced PageStats to collect statistics for leaf page analysis.

Refactored the following functions:
dict_stats_analyze_index_level() to IndexLevelStats::analyze_level()
dict_stats_analyze_index_for_n_prefix() to IndexLevelStats::sample_leaf_pages()
dict_stats_analyze_index_below_cur() to PageStats::scan_below()
dict_stats_scan_page() to PageStats::scan()

Add the stats method name to stat_description when innodb_stats_method
has a non-default value.

Added the new stat name n_nonnull_fld01, n_nonnull_fld02, etc. with a
stats description, to indicate how many non-null values exist for the
nth field of the index. This value is retrieved and stored in the index
statistics in dict_stats_fetch_index_stats_step().

rec_get_n_blob_pages(): Calculate the number of externally stored pages
for a record. It uses ceiling division with the actual usable blob page
space (blob_part_size) and now correctly handles both compressed and
uncompressed table formats for accurate BLOB page counting.

When InnoDB scans the leaf page directly, assign the leaf page count as
the number of pages scanned for a multi-level index. For single-page
indexes, use 1. This change leads to multiple changes in existing
test cases.

Non-null values are only counted at the leaf level, since only leaf
pages hold actual records. Both nullable and NOT NULL columns are
estimated with the same leaf-sampling formula:

  n_ordinary_leaf_pages * (n_non_null_all_analyzed_pages
                          / n_leaf_pages_to_analyze)

For a NOT NULL column every record is counted, so this yields the
estimated record count (no NULLs).

dict_stats_save(): now static function in dict0stats.cc that
takes the innodb_stats_method value, and is removed from dict0stats.h.

Replaced btr_rec_get_externally_stored_len() with rec_get_n_blob_pages()
in dict0stats.cc. btr_rec_get_field_ref_offs() and btr_rec_get_field_ref(),
together with the BTR_BLOB_HDR_* macros, were moved from btr0cur.cc
to btr0cur.h so that rec_get_n_blob_pages()
can reuse them;

btr_rec_get_field_ref_offs() is now a noexcept function returning size_t.

Changed stat_n_diff_key_vals and stat_n_non_null_key_vals from
ib_uint64_t* to uint64_t*

Removed the unused UNIV_STATS_DEBUG build macro (univ.i) and turned the
DEBUG_PRINTF() helper in dict0stats.cc into an unconditional no-op.
Sergei Golubchik
BUG#39449066 Refactor performance schema OBJECT_INSTANCE_BEGIN columns

Fix for MariaDB 10.6
Rex Johnston
MDEV-39492 Parallel Query: let the manager evaluate over the row it was sent

The manager could send the rows its workers produced but not evaluate anything
against them, which is why the gate refused HAVING even though end_send() applies
join->having and the manager is where end_send() now runs. HAVING binds to the
select list through Item_ref, and an Item_ref resolves through the query's
reference array, which still pointed at items reading base tables -- tables the
manager never positioned on. So the reference array is what had to move, not
HAVING.

That is a swap the server already makes, for the same reason, in the temp-table
path: materialise a row, then point HAVING and ORDER BY at the materialised
fields rather than at the tables. sql_select.cc says so where it does it -- "the
actual switching to the temporary tables fields for HAVING and ORDER BY is done
in do_select() by calling set_items_ref_array(items1)". The manager's
result_table is a materialised row layout, so it does the same: a slice of the
reference array holding one Item_field per field of result_table, installed for
the length of the drain and restored after, from the copy
init_items_ref_array() keeps.

The slice is built by hand rather than by change_to_use_tmp_fields(), which walks
each item's result_field -- and result_table is deliberately built from clones of
the select list so that the query's own items are never bound to a tmp field, so
those are the clones' fields and not the ones to send from. What the slice needs
is only that slot i is select-list item i and that field i of result_table is
where a worker projects item i, which is true by construction: manager_send_list
is already one Item_field per field, in order, and the slot numbering matches as
long as all_fields carries no hidden items. The gate now requires that rather
than assuming it -- a hidden item would have no field to point at -- which costs
nothing today because a HAVING that names a column outside the select list is
refused by name resolution before it ever reaches here (ER_BAD_FIELD_ERROR).
That last point is also what makes the whole thing safe: every column reference
in HAVING is necessarily a reference to a select-list item, so there is nothing
left in it that would read a base table directly.

HAVING was tried the other way first, cloned per worker like the conditions are,
and it does not work: deep_copy() duplicates an Item_ref, which then still points
into the manager's array, and "SELECT a+b AS s ... HAVING s > 5985" answered zero
rows where five were right. That case is in the test for exactly that reason.
Refusing what could not be cloned refused every HAVING worth running, so there
was no safe subset to ship.

parallel_query_having compares fingerprints, not counts: which rows HAVING selects
is determined even though their order is not. Seven shapes -- a select-list
column, an alias, a column the predicate does not mention, a conjunction, an empty
result, a HAVING over a row the worker assembled from two tables, and a select list
holding a constant -- all identical to serial, all confirmed to have run in the
workers.

The forced-worker sweep is unchanged at its known failures, and main and innodb
pass at 2105.

Worth saying what this is for beyond HAVING: aggregates, GROUP BY and ORDER BY all
need the manager evaluating expressions against the shipped row, which is the same
slice. This is the piece they were waiting on.

This commit was prepared with Claude Code: it found that HAVING cannot be cloned
into a worker, identified the temp-table path's reference-array swap as the
mechanism the manager wanted, established by measurement that all_fields carries
no hidden items for these shapes so the slot numbering holds, and wrote the test
around the alias case that the cloning attempt got wrong.
Sergei Golubchik
MDEV-40589 default exclude list for secure-file-priv

don't allow to access /proc if secure-file-priv="",
set secure-file-priv=/ to access everything and disable the exclude list

remove test for a conditon that can no longer happen
Yuchen Pei
MDEV-15621 [to-squash] Follow some gemini review comments

Also removed table_rows in a SELECT, to avoid an unrelated flaky failure
Georgi (Joro) Kodinov
MDEV-39572: Add marking requirements for AI-assisted contributions to COMMUNITY_CONTRIBUTIONS.md

Explained the use of git commit trailers.
Fixed some minor header typos in the document.
Addressed review comments.
Hemant Dangi
Fix galera SST-abort tests to allow exit code 0 on some platforms

Issue: galera_sst_mariabackup_missing_ssl and galera_sst_rsync_missing_stunnel
only accepted exit codes 1,134 when the joiner mariadbd aborts SST, causing
intermittent failures (e.g. on FreeBSD) where the process exits cleanly with 0.

Solution: accept exit code 0 as well, matching the precedent already
established in galera_sst_cn_injection.test / galera_sst_mariabackup_encrypt_with_key_server.test.
Georgi (Joro) Kodinov
Updated README.md to contain a more friendly version.
Yuchen Pei
MDEV-15621 [refactor] Partitioning cleanup

change p_column_list_val::fixed to a bool
remove redundant end label in partition_info::fix_column_value_functions
Rex Johnston
MDEV-39492 Parallel Query: the manager sends rows through end_send()

The other half of the executor split, and the last of the two hand-written
executors. manager_collect_and_send() drained a worker's batch and called
select_result::send_data_with_check() itself, keeping its own count of rows sent,
rows accepted and duplicates -- a second copy of end_send() with none of what
end_send() also does.

It now stands on the real one. end_send() is called with a null JOIN_TAB, which
is how the server itself calls it for a plan whose tables were all const: it then
takes the list to send from JOIN::fields, so that is pointed at the Item_fields
over the record layout the workers ship, for the length of the drain and no
longer. The row accounting, HAVING, and the LIMIT checks including WITH TIES are
the server's from here on.

end_send() loses its static. Its siblings end_send_group() and end_write_group()
are already declared in sql_select.h, and sub_select() already is too -- which is
what a worker was given to run in the commit before this one -- so this is the
odd one of the family rather than a new kind of exposure.

What it is for. The gate refuses LIMIT, HAVING, GROUP BY, ORDER BY and
aggregates, and every one of those is an operator that stands where end_send()
stands or just above it. With the manager's loop ending in the server's own
operator, each becomes a question of which operator to end in and what the
workers must produce for it, rather than another few dozen lines here. The
NESTED_LOOP_QUERY_LIMIT return is wired to the stop flag that the batch handoff
protocol has always had and never used, so a manager that has enough rows now
stops its producers instead of draining them.

LIMIT was checked by opening the gate temporarily: SELECT ... LIMIT 5 over a
20000-row table in four workers sends exactly five rows and terminates, and
LIMIT 999999 sends all twenty thousand. The gate is left closed, because LIMIT
without ORDER BY returns whichever rows finish first rather than the ones a
serial scan would reach, and while that is a legal answer to an unordered query
it is a different answer, so it wants a decision rather than a commit.

No test of its own: the rows the manager sends are the same rows, and the suite
is what says so. main and innodb pass at 2103, the parallel tests pass under
--ps-protocol, and the forced-worker sweep is at its known eighteen.

This commit was prepared with Claude Code: it established that end_send()
accepts a null JOIN_TAB and reads JOIN::fields in that case, which is what let
the manager reuse it without inventing a plan for itself, and verified the LIMIT
path against a temporarily open gate rather than leaving the branch unexercised.
Rex Johnston
MDEV-39492 Parallel Query: a worker runs the server's nested loop

The worker's hand-written join goes, and sub_select() takes its place. Third and
last step of the executor split on the worker side; the manager still collects
and sends.

worker_join_inner() was a nested loop that understood inner equi-joins and
nothing else, which is why can_run_query_in_workers() is a list of refusals: an
outer join, a semijoin strategy, a join buffer, LIMIT or an aggregate would each
have had to be written into it a second time. What replaces it is the loop the
serial plan runs. Each of the worker's JOIN_TABs points at the next through
next_select, the last one at pwt_end_send() where a serial plan would have
end_send(), and worker_run_query() drives it the way do_select() does -- once to
produce rows, once more to signal end of records. Every capability the gate
currently refuses becomes a question about what the manager does with the rows,
not about what the worker can join.

Two pieces of plumbing were needed. The driving table reads through the engine's
chunk reader rather than a table scan, so it gets a record source of its own;
every other table keeps the one make_join_readinfo() chose, because those
functions act on the JOIN_TAB they are handed and so already read this worker's
table through this worker's ref. And the executor reaches the worker through
function pointers whose signatures carry no worker, so a thread-local holds it
for the length of the join, the way mysqld.cc holds THR_THD.

Three things had to be got right that the copy in "give a worker real JOIN_TABs"
had left plausible but wrong, all found by the suite:

  - READ_RECORD mixes plan and per-scan state. make_join_readinfo() sets the
    row-fetching function and unlock_row at optimize time -- join_read_next_same
    for a ref table, join_no_more_records for eq_ref -- while the buffers are
    filled when the scan starts. Clearing the whole struct left null pointers for
    sub_select() to call, so the plan half is now restored by hand.

  - The worker's JOIN must describe the worker's array. JOIN_TAB::pfs_batch_update()
    finds the innermost table by join_tab + table_count - 1, and with the
    manager's table_count, which counts the const tables it resolved before the
    join, that lands past the end. table_count and top_join_tab_count are the
    worker's table count and const_tables is zero, and cached_pfs_batch_update is
    recomputed per tab rather than inherited, because sub_select() asserts it
    against the live answer.

  - TABLE::reginfo.join_tab is read at execution time, not just by the optimizer:
    join_read_next_same() finds the tab it is reading for through it. A worker's
    table copy has to point at the worker's tab. That is the third field of TABLE
    to need this after map and in_use, so the standing rule holds -- assume
    anything the optimizer sets on a TABLE is missing on a copy until checked.

read_first_record() means read the first record, which the pscan source at first
did not, leaving evaluate_join_record() to run once on a buffer nothing had
filled: one extra row per worker, and NULL, so a count changed while a checksum
over the same rows did not. Worth remembering as a shape -- an off-by-one in row
count that no fingerprint catches.

The trackers ANALYZE reads now come from the executor itself, which counts
r_scans, r_rows and r_rows_after_where into JOIN_TAB::tracker -- already this
worker's. So the handover in "give ANALYZE the numbers the workers produced"
keeps working unchanged, except that sub_select() counts one scan of the driving
table per worker where the report wants one between them, so that one is no
longer summed.

No test of its own: this is a refactor, and its test is that the suite still
passes. parallel_query_join and parallel_query_worker_side between them drive
eq_ref, ref with fan-out, a three-table chain, a non-indexed inner table, a
pushed index condition and a join buffer's scan filter, and all of it now goes
through sub_select() and evaluate_join_record(). main and innodb pass in full at
2103 tests, the parallel tests pass under --ps-protocol, and the forced-worker
sweep is at its known failures.

Not what was expected: the warning that reports "at row 0" was thought to fall
out of this, since sub_select() calls reset_current_row_for_warning(). It does
not. Warnings a worker raises already relayed with the same text as serial for
every class a SELECT can produce, so that defect is about a warning type reached
another way and stays open.

This commit was prepared with Claude Code: it established which JOIN_TAB and
READ_RECORD state is plan and which is per-scan, wrote the record source, the
terminal and the chaining, and diagnosed each of the four failures above from the
suite rather than from reading.
Sergei Golubchik
cleanup: sys_vars.secure_file_priv test
Oleksandr Byelkin
Merge branch '10.11' into 11.4
Rex Johnston
MDEV-39492 Parallel Query: GROUP BY

The manager hands each drained row to the plan's terminal function, and for a
GROUP BY that is sub_select_postjoin_aggr(): it writes the row into the server's
aggregation temp table keyed on the group, and at end of records reads the table
back and drives every stage after it. So the grouping, the aggregates, HAVING
over the groups and an ORDER BY of the groups are the server's own work, done on
the manager, and this commit adds none of them. What it does is stop refusing
them.

The gate no longer refuses group_list, and no longer refuses need_tmp. The shapes
a temp table is otherwise built for -- DISTINCT, ORDER BY, window functions, a
procedure -- are each refused on their own terms, which says what is excluded
instead of excluding a superset of it. need_tmp said little there in any case: the
gate runs from make_join_readinfo(), before make_aggr_tables_info() plans the
aggregation, so it still held its pre-planning value.

What a chunked scan cannot promise is row order, and two of the server's group
terminals need it. end_send_group and end_write_group find a new group by
comparing each row's group values with the previous row's, so given unordered rows
they would emit a group per run of equal neighbours. The optimizer picks one of
them when its plan delivers the rows already grouped, which EXPLAIN shows as
"Using filesort" without "Using temporary". end_update and end_unique_update look
each group up in the temp table by key and do not care what order the rows arrive
in, and that is the plan for a GROUP BY no index resolves -- the case worth
parallelising.

Which terminal was chosen is not known when the gate runs, for the same reason
need_tmp was not. So pwt_plan_needs_group_order() asks in run_worker_side_join(),
after the aggregation has been planned, and declines the parallel scan the way the
engine can when it will not do a consistent read. AGGR_OP gets a getter for
write_func so the question can be asked.

With no GROUP BY, end_send_group is the terminal and is safe: there is one
implicit group, group_fields is empty and no group change is ever reported. That
is the aggregate case, which continues to run in the workers.

An ORDER BY that reaches execution unrefused is sound only when there is an
aggregation temp table for the sort to be applied to, because then the order is
established on the manager after every row has arrived and the order they arrived
in does not matter. Asserted rather than assumed, since the gate cannot see such
an ORDER BY either.

Measured shapes are in the new test, including the two halves of the order
decision: a GROUP BY that gets the keyed temp table runs in the workers, and
COUNT(DISTINCT) per group and WITH ROLLUP get the sorted plan and are declined.
GROUP BY the primary key is refused earlier and for an unrelated reason -- the
plan reads the clustered index in order, and a driving table that is not a full
scan is not one the gate accepts.

This commit was prepared with Claude Code: it traced the terminal-function chain
to find that one call reaches every post-scan stage, established from
set_postjoin_aggr_write_func() which terminals depend on row order, wrote the
gate relaxation and the execution-time check, and measured each shape in the test
against its serial answer -- correcting two explanations it had first written,
about indexes forcing the ordered plan, that EXPLAIN then disproved.
Rex Johnston
MDEV-39492 Parallel Query: give a worker real JOIN_TABs

No functional change. This is the first step of replacing the worker's private
nested loop with the executor's, and it replaces the structure that loop reads.

pwt_jointab described a table in five fields: its TABLE copy, the access type, a
ref, a condition and the sorted flag. sub_select() and evaluate_join_record()
want a JOIN_TAB, so every capability added to the private loop would have to be
added again when the split moves to the real one. A worker now holds an array of
JOIN_TAB, n_tables of them, indexed the way pwt_manager::mgr_tabs is so that [0]
is the parallel-scanned driving table -- the indexing sub_select() will be
started from. The driving table's condition moves onto join_tabs[0].select_cond
with every other table's, so worker_cond goes away.

The tabs are copied from the manager's and then rebound, rather than built from
nothing, so that a field this code does not know about holds the value the
optimizer chose instead of a zero that would look deliberate. Overwritten: the
TABLE, the condition, the ref, the tracker. Cleared: the places a condition can
hide (pre_idx_push_select_cond, cache_select) now that select_cond carries all of
it, and the join buffer, which a worker does not use. Nulled deliberately: the
JOIN, next_select, read_first_record and read_record, which the next two steps
fill in -- reaching them before then is a crash rather than a read of the
manager's execution state.

That last distinction is the point of pwt_assert_tab_inert(). A copy keeps the
manager's pointer in anything the copy does not overwrite, which is the trap
TABLE::map and TABLE::in_use were, and the fields that matter here are ones the
gate is supposed to have excluded rather than ones this code sets. So each is
asserted inert on the manager's tab before the copy: the outer-join chain, the
semijoin strategies and their weedout and firstmatch tables, emb_sj_nest, the
rowid filter, range access, split derived materialization, the DISTINCT
shortcut, HAVING, and the access type. Relax a gate without teaching the copy
about the field it lets through and a debug build stops here instead of
executing with another thread's state.

Writing those assertions is also what found the BNL scan filter fixed in "filter
by the condition the join buffer would apply": asking whether cache_select was
inert turned out to have the answer no, and a wrong result behind it. They are a
survey as much as a guard, so they are worth running before the code they
protect. Verified live on a debug build -- none of them fires across the main
suite with parallel_worker_threads forced on, 1418 tests, and a deliberately
false one aborts the server, so the negative result is a measurement rather than
an assumption.

main and innodb pass in full, 2103 tests, and the parallel tests pass under
--ps-protocol. The forced-worker sweep is at its 18 known failures, all EXPLAIN
and cost output.

This commit was prepared with Claude Code: it established which JOIN_TAB and
JOIN fields the real executor actually reads (4 of JOIN, some 29 of JOIN_TAB, of
which the gate makes most inert), wrote the copy and the assertions, and checked
the assertions execute rather than trusting that they had.
Jan Lindström
Fix test failure on galera_sst_cn_injection test case.

Test requires pkill so skip it is not found from system. Additionally
pkill may fail if socat is not anymore open when pkill executed
(or when socat is not actually used).
Yuchen Pei
MDEV-15621 Auto add RANGE COLUMNS partitions by interval

Allow auto partitioning by interval in PARTITION BY RANGE COLUMNS

PARTITION BY RANGE COLUMNS (col_name)
INTERVAL interval [AUTO]
(
  PARTITION partition_name VALUES LESS THAN (value)
  [, PARTITION partition_name VALUES LESS THAN (value) ... ]
)

where

- col_name is the name of one column of type DATE or DATETIME or
  TIMESTAMP

- at least one partition is supplied, and the highest partition cannot
  have MAXVALUE range

- INTERVAL interval is a positive time interval. it can be mariadb
  format or oracle NUMTODSINTERVAL/NUMTOYMINTERVAL format. Like
  versioning, the smallest unit is second, i.e. no subsecond like
  microsecond.

- DATE column cannot have interval with values less than a day

- Subpartitions are allowed, but restricted to existing subpartition
  types, i.e. [LINEAR] (KEY|HASH)

When performing one of the following DML statements on such a table,
it will first add partitions by the specified interval until the
partition covers the current time:

- INSERT
- INSERT ... SELECT
- LOAD
- UPDATE
- REPLACE
- REPLACE ... SELECT

Partition addition will not cause an implicit commit like DDL normally
does.

The partitions are named pN.

Otherwise the table behaves exactly the same as a normal RANGE COLUMNS
partitioned table.

Note that TIMESTAMP is not allowed as a type for PARTITION BY RANGE
COLUMNS otherwise.
forkfun
MDEV-39566 fix status_by_thread crash on live thread-count change

PFS_table_context snapshots the live thread/user/host/account
count at scan start and again on restore (filesort's second
rnd_init). If the count changed between the two, m_map_size
mismatched and the server aborted.

Skip the wasted re-sample on restore, bound each table's scan by
the frozen snapshot instead of the container's live count.
Sergei Golubchik
win secure-file-priv