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
Sergei Golubchik
MDEV-40445 TLS session resumption

* needs no application changes
* connector keeps a hash of connection key -> sessions and resumes
  sessions automatically when applicable
* session key includes everything that affects cert verification
  (because resumed session is not re-verified)
* openssl and gnutls support only, not schannel

Assisted-By: Claude:claude-5-opus
Arcadiy Ivanov
fixup! Limit the memory used by GROUP_CONCAT() with ORDER BY

`main.gconcat_warn` case 5 recorded `Row 88 was cut by group_concat()`
and reads `Row 127` on a 32-bit build, so the test fails on
`x86-debian-12-fulltest` and `x86-debian-12-fulltest-debug`.

The case starves the sort tree until a repack drops rows, and the row
the warning names is how many rows the budget held: the bytes the
repack copies to, divided by `sizeof(TREE_ELEMENT)` plus the size of
an element. `TREE_ELEMENT` is 24 bytes where a pointer is eight and 12
where it is four. The two recorded numbers agree with that. The case
comment gives the repack 2720 bytes, and 2720 over 88 is about 31
bytes a row against 2720 over 127 for about 21, a difference of the
order the smaller `TREE_ELEMENT` accounts for.

The test already says this, and already masks the result it prints for
the same reason:

    The exact result depends on sizeof(TREE_ELEMENT) and the
    reclength, so only check that the result came out short.

The warning is printed beside that result and was not masked. Mask the
row number with `--replace_regex`, as `main.gconcat_distinct_spill`
masks the same warning for the same reason. `SHOW COUNT(*) WARNINGS`
still checks that the group gave exactly one warning, which is what
the case is there to show.

Only case 5 needs it. The other cut warnings in this test come from
`group_concat_max_len` on a handful of rows, and their row numbers do
not depend on the width of a pointer.
Arcadiy Ivanov
MDEV-41007 Warn when GROUP_CONCAT(DISTINCT) loses rows silently

Give a warning when `GROUP_CONCAT(DISTINCT x)` or
`JSON_ARRAYAGG(DISTINCT x)` returns only part of a group, or nothing at
all, because the walk of the duplicate filter failed. The result is
wrong rather than deliberately cut, and nothing was said about it.

Both build their result in `val_str()` by walking `unique_filter`, and
threw the walk's return value away. `Unique::walk()` reports its own
failures through it, from allocating the merge buffer to reading back
the chunks it merged, so a failure gave a short result, or an empty
one, in silence.

The return value cannot be used on its own. `dump_leaf_key()` also
stops the walk, for two reasons that are not failures: it cuts the
result at `group_concat_max_len`, which it already reports by setting
`result_cut`, and it stops without losing anything once the `LIMIT` is
used up. Reporting every non-zero return as a cut warns about
`GROUP_CONCAT(DISTINCT a LIMIT 5)` returning exactly the five rows
that were asked for.

`dump_leaf_key()` now records that it was the one that stopped the
walk, so `val_str()` warns only when the walk itself failed.

The warning is a new one. `ER_CUT_VALUE_GROUP_CONCAT` reports the row
the result was cut at, and there is no such row here: the walk failed
before it delivered anything, and how much was lost is not known, so
it would read `Row 0 was cut by group_concat()`.
`ER_RESULT_CUT_BY_LIMIT` says that the result was cut and names the
limit that cut it, which is the memory `Unique` was given to work in:
the smaller of `tmp_memory_table_size` and `max_heap_table_size`.

Not every failure is silent either. The merge buffer is allocated with
`MY_WME` and the spill file is opened with `MY_WME`, so running out of
memory or failing to read raises an error of its own. Only the guard at
the top of `merge_walk()`, which refuses a merge buffer too small to
hold one key per chunk, returns without saying anything. Warn only when
no error was raised: where one was, the user has been told and the
statement is failing, so describing the length of a result nobody will
see adds nothing.

The debug keyword `unique_walk_merge_fail` fails the merging walk
quietly and `unique_walk_merge_error` fails it with an error raised.
`main.gconcat_distinct_walk_fail` uses both. The `LIMIT` case needs no
debug build and is checked in `main.gconcat_distinct_spill`.
Sergei Golubchik
CONC-833 generalize tls session cache

move session cache from ma_tls.c (where it was called "TLS session cache"
used to store sesion tickets for session resumption) to a new file
ma_session_cache.c, make tls code use it to store session tickets,
but open the possibility to store other connection data in it.

Assisted-By: Claude:claude-5-opus
Sergei Petrunia
Make the MVI scan a real access method: QUICK_MVI_SELECT

JSON_CONTAINS() over a multi-valued index used to be optimized by
rewriting the WHERE clause: setup_mvi_for_join() injected a synthetic

  MATCH vcol AGAINST ('+k1 +k2' IN BOOLEAN MODE)

into join->conds and into select_lex->ftfunc_list, and the normal
fulltext machinery then picked it up as JT_FT access. The injected item
showed up in the plan and in the condition even though the user never
wrote it, and because it became ordinary ref access the scan was never
costed against the alternatives - it won by being in the WHERE clause.

Introduce QUICK_MVI_SELECT (QS_TYPE_MVI), a QUICK_SELECT_I that drives
the fulltext index directly through the handler API. Unlike FT_SELECT
there is no Item_func_match to have created the FT_INFO, so the quick
select creates it in reset() with ft_init_ext() and frees it with
close_search() in its destructor. Mvi_access::create_ft_item() is
replaced by build_ft_query(), which builds just the query string.

The analysis in setup_mvi_quick() is now kept: Mvi_context moves to the
header, is allocated on the mem_root and stored as JOIN::mvi_ctx, where
JOIN::get_mvi_access_for_table() looks it up. get_quick_record_count()
builds the quick select before test_quick_select() and keeps whichever
of the two is cheaper; test_quick_select() itself is untouched, so the
MVI quick is held in a local across the call (it deletes select->quick
on entry). The same save/compare is done around the second
test_quick_select() call in make_join_select(), which a LIMIT can reach.

A fulltext key never gets a bit in const_keys or keys, so mark the MVI
key of every table that has an access: the const_keys bit is what lets
the range analysis run for that table at all, the keys bit puts the
index into EXPLAIN's possible_keys.

Collect the accesses from the top-level AND-parts of the WHERE clause
only, instead of walking the whole condition. An MVI scan reads just the
rows the index matches, so it is only valid for a predicate that must
hold for every row of the result: for

  json_contains(j1->'$.tags','"a"') OR json_contains(j2->'$.tags','"a"')

scanning either index would drop the rows that only match the other
branch. The deleted add_ft_for_mvi() refused COND_OR_FUNC for the same
reason; walking the condition tree lost that, which only became visible
once the accesses were actually used.

Costs are placeholders (records=10, read_time=0.001) until the engine
can estimate a fulltext search. Note that while there is no estimate,
an MVI access is also taken when test_quick_select() produced no quick
select at all, without comparing it to the cost of a table scan.

TODO: This doesn't handle UPDATE/DELETE!  Should it be put into
  check_quick() call?
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Sergei Golubchik
10.11: fix main.ssl_crl failures for openssl/gnutls combination

just as OpenSSL client can fail with "Lost connection" or
"certificate revoked", GnuTLS can fail with "Certificate was revoked" or
"Error in the push function".

It's a race because the server closes the connection with a rejected
certificate immediately and the client may or may not read the alert
message before getting a RST
Sergei Golubchik
fix main.ssl_autoverify failures for wolfssl/openssl combination

unlike openssl server, wolfssl doesn't send the whole chain so
the cert verification error was different. gnutls client correctly
treated it as MARIADB_TLS_VERIFY_TRUST, while openssl client
was too restrictive as it was only tested on openssl server.

fix: treat more cert verification errors as MARIADB_TLS_VERIFY_TRUST
Arcadiy Ivanov
MDEV-21879 GROUP_CONCAT(DISTINCT ORDER BY) is wrong when Unique spills

`Item_func_group_concat::add()` decided whether a row was a duplicate
by checking whether `Unique::elements_in_tree()` had grown after
`unique_add()`:

    uint count= unique_filter->elements_in_tree();
    unique_filter->unique_add(get_record_pointer());
    if (count == unique_filter->elements_in_tree())
      row_eligible= FALSE;

`Unique` flushes its whole in-memory tree to disk when it runs out of
memory, and `elements_in_tree()` only counts what is still in memory.
After the first flush the test says nothing about the rows that were
already spilled.

**MDEV-11563** made this harmless for `GROUP_CONCAT(DISTINCT x)` by
building the result in `val_str()` from `unique_filter->walk()`, which
merges the spilled parts back in. It left the `ORDER BY` case alone.
There the result comes from the sort tree, which `add()` fills gated by
`row_eligible`, so the defect is still fully live.

Both directions of the failure are reachable, depending on how often
the filter flushes relative to the insert:

1. Duplicates reach the result. 100 rows holding 50 distinct values
  give all 100 values back.
2. Rows are lost. 30 distinct rows of 2000 bytes give one value back.

`JSON_ARRAYAGG(DISTINCT x ORDER BY y)` fails in the same way.

Fixed by not filling the sort tree from `add()` when `DISTINCT` is
used. `val_str()` now walks the merged `unique_filter` into the sort
tree and then walks the sort tree, so the rows are sorted after the
duplicate filtering is complete instead of during it.

`Unique::walk()` merges everything it flushed, so the sort tree can be
handed more rows than fit in memory. `insert_to_order_tree()` repacks
it on the same memory budget `add()` used, and a walk that runs out of
memory sets `result_cut`, so the user gets a cut value warning rather
than a silently short result.

**Behaviour change.** `ORDER BY` does not order rows that tie on the
ordering expression, and which of them comes first changes here. It
used to follow the order the rows were read in; it now follows the
order the duplicate filter keeps them in. Unlike the old order, the new
one depends on neither the memory available nor the physical row order.
`main.gconcat_distinct_spill` checks that, and `main.func_gconcat`
records one such tie.
Sergei Golubchik
CONC-833 cache plugin data in the session cache

this extends auth plugin API, the version is incremented

username and password are now part of the client connection cache key,
so connections with different passwords do not use eash other's
session tickets, this increased the number of roundtrips in the test.
Sergei Golubchik
CONC-833 cache plugin data in the session cache

Authentication plugins can now use the session cache too.
The API:
* a new (unused, repurposed) field in MYSQL structure, plugin_data.
* before authentication the data from the cache (or NULL) is put there
* after authentication whatever plugin left there is stored in the cache

The data is a size_t length followed by opaque blob of bytes.
Must be allocated with malloc, the cache can free() it as needed.

Cached data are only shown to a plugin for connection with the
same user/password/host/port/socket/protocol/ssl options,
see ma_session_cache_key().

This extends auth plugin API, so the version is incremented

Assisted-By: Claude:claude-5-opus
Sergei Golubchik
cleanup: remove pre-locking of old_password_plugin

nobody should be using it, so this "optimization" actually isn't.
Sergei Golubchik
cleanup: ma_hashtbl_init, typos, mutex lock in ma_tls_end()

ma_hashtbl_init with CALLER_INFO was copied from the server, but
never used here (CALLER_INFO wasn't even defined)

the locking the mutex just before destroying is
fundamentally broken, let's not do it.
Sergei Golubchik
remove old<->native auto-swicth on SET PASSWORD

mysql_old_password is disabled, @@old_passwords does nothing.
let's not switch to it automatically.
Sergei Golubchik
add change_user test to plugins.parsec
Thirunarayanan Balathandayuthapani
MDEV-26057 Assertion `!vcol->v_indexes.empty() in trx_undo_log_v_idx

Problem:
========
-  Rollback of an INPLACE ALTER TABLE is executed while holding only a
shared metadata lock on the table, so DML can run concurrently.
rollback_inplace_alter_table() resets dict_col_t::ord_part in a
critical section of its own, after row_merge_drop_indexes() already
removed the aborted indexes from the dictionary cache and emptied
dict_v_col_t::v_indexes. During this time, DML statement can see a
virtual column with ord_part set and an empty v_indexes, which
makes assert failure in trx_undo_report_insert_virtual().

Solution:
========
row_merge_reset_ord_part(): Added a function to reset
dict_col_t::ord_part for the columns that are no longer a field of
any index remaining in the dictionary cache.
For virtual columns the decision is based on dict_v_col_t::v_indexes
being empty, and no element is ever removed from that list.

row_merge_drop_indexes(): Added a call to row_merge_reset_ord_part()
in the branch that removes the indexes from the cache, in the same
dict_sys.latch critical section. That branch is taken only when
MDL_EXCLUSIVE is held or when this is the only handle to the table,
so no concurrent DML can observe the intermediate state.
In the lazy drop branch the indexes and their v_indexes entries
stay in the cache and nothing is reset; that is done later,
when the indexes are dropped while holding MDL_EXCLUSIVE.

check_col_exists_in_indexes(): Removed the only_committed parameter,
which no longer has any caller.

row_quiesce_col_ord_part(): Added a function to get
dict_col_t::ord_part and dict_col_t::max_prefix of a column
from the committed indexes that are
present in the dictionary cache.

row_quiesce_write_table(): Write the row_quiesce_col_ord_part() return
values to the .cfg file instead of the cached dict_col_t fields,
because a rolled back ADD INDEX leaves ord_part set until the
aborted index is removed by a later DDL, and
max_prefix is never reset when an index is dropped, which makes
IMPORT TABLESPACE reject the tablespace with a bogus schema mismatch.
Sergei Golubchik
bugfix: correct plugin version comparison

(also fix it in the server to use the same pattern)
Arcadiy Ivanov
MDEV-40692 GROUP_CONCAT replays a group when an OFFSET skips every row

Nothing says how many times a statement asks for the result of a group,
and the answer must not depend on it. A `HAVING` clause on the alias is
the shortest statement that asks twice, and it returns a different value
than the same aggregate asked once:

    SELECT GROUP_CONCAT(a ORDER BY a LIMIT 2 OFFSET 4) v FROM t1;
    -> (empty)
    SELECT GROUP_CONCAT(a ORDER BY a LIMIT 2 OFFSET 4) v FROM t1
      HAVING v LIKE '%';
    -> a,b

`val_str()` walks only while `result_finalized` is false, and
`dump_leaf_key()` raises that flag for the first row it writes. A row
that falls inside the offset is skipped by an earlier return, which
decrements the offset counter and leaves the flag alone. The row-limit
arm immediately above it does raise the flag before its own early
return, so two adjacent early returns behave differently.

A walk in which every row was skipped therefore writes nothing and
records nothing. The next caller walks again with the offset already
spent, and the rows skipped the first time are appended to a result
buffer that was handed over once already.

Once the duplicate filter has spilled to disk the second walk is worse
than wrong. `Unique::reset()` documents the contract:

    Clear the tree and the file.
    You must call reset() if you want to reuse Unique after walk().

The first walk flushed the tree and emptied it, so the second flushes an
empty tree, appending a chunk that holds no rows. `merge_walk()` reads
nothing back from it and fails `DBUG_ASSERT(bytes_read)`. A build
without assertions goes on to take keys from that chunk.

Set `result_finalized` where the walk block ends, so that it records
every path that has consumed the filter rather than only the paths that
wrote a row.

On the release branches only the form without `ORDER BY` reaches the
duplicate filter. Since MDEV-21879 the `DISTINCT ... ORDER BY`
combination builds its result the same way, so both forms can reach the
assertion here.
Sergei Golubchik
10.11: fix main.ssl_crl_clients failures for openssl/gnutls combination

* this test needs openssl *client* not openssl *server*

* sslopt-case.h must not disable client features by looking
  at server defines. Server is compiled with WOLFSSL, the client
  can be anything. If GnuTLS ignores opt_ssl_crlpath it's the
  same effect as opt_ssl_crlpath=NULL anyway.
Vladislav Vaintroub
MDEV-40608 build mysqlservices without an embedded CRT requirement

mysqlservices only exposes a thin C API, no CRT state crosses it, so
don't force whatever CRT/config built the server onto a plugin linking
it. Without /Zl, a plugin built in a config with no matching installed
mysqlservices variant (CMake silently substitutes one - verified with
a toy project) gets an ignorable but noisy LNK4098 warning.

Assisted-by: Claude:claude-5-sonnet
Monty
fixup! db1d6d824d96694acaa9e715836e43562a718e15
Arcadiy Ivanov
MDEV-40920 Say whether a value cut in a group reached the answer

A TEXT value longer than `group_concat_max_len` is cut on its way into
`blob_storage`, in `Field_blob::handle_group_concat()`. That happens
while the group is being built, not when the answer is put together, so
whether the answer is any shorter for it depends on whether the row
carrying the value reaches the answer at all. Every such cut was
reported as `ER_CUT_VALUE_GROUP_CONCAT`, which says that the answer lost
something the user asked for, and that is true of only some of them.

`Blob_mem_storage` now writes one byte in front of every value it
stores and returns the pointer past it, so `was_cut()` answers for any
value a reader holds a pointer to. `Field_blob::store()` sends every
blob of a table that has a `Blob_mem_storage` through
`handle_group_concat()`, so no value in that storage is without the
byte, and `Field_blob::get_ptr()` on the record of a row hands back
exactly the pointer that was stored.

`dump_leaf_key()` reads the mark off each row it appends and sets
`value_cut_in_result`. `val_str()` then reports:

1. **A warning**, `ER_CUT_VALUE_GROUP_CONCAT`, when a row that reached
  the answer carried a cut value. The answer is short by what was cut.
  The result being cut at `gconcat_max_len()` already gives that same
  warning, and a group that hits both is told once, not twice.
2. **A note**, `ER_CUT_VALUES_WHILE_PROCESSING`, when a value was cut
  but no row carrying one reached the answer. The answer may well be
  what a larger limit would have given. One note per aggregate is
  enough for a statement however many groups had a value cut, and
  `cleanup()` clears the mark so a statement run again gets its own.

Reporting the loss as a warning keeps a strict `sql_mode` aborting on
it, which it does because `THD::raise_condition()` promotes a warning
and never promotes a note.

`ST_COLLECT` is not affected. It reports `ER_CUT_VALUE_GROUP_CONCAT`
itself, against `group_collect_max_len`.

`main.gconcat_cut_note` covers the split with one group holding a short
value and a long one, where a `LIMIT` alone decides which of them the
answer is built from, over both the sort tree and the duplicate filter.
`main.func_gconcat` shows the granularity: of five groups at
`group_concat_max_len=499999`, the one holding exactly 499999 bytes is
the one that does not warn.

Note that `blob_storage` only exists when the aggregate has an
`ORDER BY` or a `DISTINCT` and a blob field, so this is the only shape
in which a value is cut this way.
Sergei Golubchik
MDEV-40445 TLS 1.3 early data

Send first client reply packet together with the TLS 1.3 ClientHello,
avoiding one roundtrip (thus the name 0-RTT). If the server does not
accept early data it is resent the normal way, after TLS handshake.

This only works when session is resumed and the data is sent before the
full TLS connection is established, so it's not fully protected yet.
Thus only use it for password_and_hashing auth.

OpenSSL and GnuTLS support is implemented, SChannel isn't.

Assisted-By: Claude:claude-5-opus
Sergei Golubchik
cleanup: remove historical my_plugin_lock aliases
Sergei Golubchik
11.4: fix main.ssl_autoverify failures for wolfssl/openssl combination
Monty
Trivial optimziations for group_concat

- Remove some if
- Reorder code
- More code comments

(cherry picked from commit dc6a897961c311f981b150e4207ffc1390a219ef)
Sergei Petrunia
Make the MVI scan a real access method: QUICK_MVI_SELECT

JSON_CONTAINS() over a multi-valued index used to be optimized by
rewriting the WHERE clause: setup_mvi_for_join() injected a synthetic

  MATCH vcol AGAINST ('+k1 +k2' IN BOOLEAN MODE)

into join->conds and into select_lex->ftfunc_list, and the normal
fulltext machinery then picked it up as JT_FT access. The injected item
showed up in the plan and in the condition even though the user never
wrote it, and because it became ordinary ref access the scan was never
costed against the alternatives - it won by being in the WHERE clause.

Introduce QUICK_MVI_SELECT (QS_TYPE_MVI), a QUICK_SELECT_I that drives
the fulltext index directly through the handler API. Unlike FT_SELECT
there is no Item_func_match to have created the FT_INFO, so the quick
select creates it in reset() with ft_init_ext() and frees it with
close_search() in its destructor. Mvi_access::create_ft_item() is
replaced by build_ft_query(), which builds just the query string.

The analysis in setup_mvi_quick() is now kept: Mvi_context moves to the
header, is allocated on the mem_root and stored as JOIN::mvi_ctx, where
JOIN::get_mvi_access_for_table() looks it up. get_quick_record_count()
builds the quick select before test_quick_select() and keeps whichever
of the two is cheaper; test_quick_select() itself is untouched, so the
MVI quick is held in a local across the call (it deletes select->quick
on entry). The same save/compare is done around the second
test_quick_select() call in make_join_select(), which a LIMIT can reach.

A fulltext key never gets a bit in const_keys or keys, so mark the MVI
key of every table that has an access: the const_keys bit is what lets
the range analysis run for that table at all, the keys bit puts the
index into EXPLAIN's possible_keys.

Collect the accesses from the top-level AND-parts of the WHERE clause
only, instead of walking the whole condition. An MVI scan reads just the
rows the index matches, so it is only valid for a predicate that must
hold for every row of the result: for

  json_contains(j1->'$.tags','"a"') OR json_contains(j2->'$.tags','"a"')

scanning either index would drop the rows that only match the other
branch. The deleted add_ft_for_mvi() refused COND_OR_FUNC for the same
reason; walking the condition tree lost that, which only became visible
once the accesses were actually used.

Costs are placeholders (records=10, read_time=0.001) until the engine
can estimate a fulltext search. Note that while there is no estimate,
an MVI access is also taken when test_quick_select() produced no quick
select at all, without comparing it to the cost of a table scan.

TODO: This doesn't handle UPDATE/DELETE!  Should it be put into
  check_quick() call?
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Sergei Golubchik
MDEV-40445 TLS session resumption (wolfssl)

* enable HAVE_SESSION_TICKET to allow resumption
* enable OPENSSL_ALL+KEEP_PEER_CERT to keep peer cert in the ticket,
  otherwise REQUIRE SUBJECT doesn't work after resumption
* disable OPENSSL_EXTRA which was enabled as a replacement
  when OPENSSL_ALL was disabled in 136e8661197
* disable NO_WOLFSSL_STUB to get SSL_CTX_sess_hits/etc stubs
* but they're stubs, always return 0, so add a wolfssl combination to
  the test that checks for these values
Sergei Golubchik
CONC-833 PARSEC credential cache

parsec plugin uses session cache to store private ad public keys
and the ext-salt. This allows not to do key derivation for
every connection (it's very slow!) and to skip the salt request,
saving one round-trip.

also parsec plugin uses MYSQL::plugin_data to pass information from
auth() to hash_password() without using thread local variable, it was
fragile and perhaps even broken in async library.

parsec protocol is extended to allow to skip salt request - the client
can now send the signed responce without asking for salt

Assisted-By: Claude:claude-5-opus
Sergei Golubchik
MDEV-40445 TLS 1.3 early data (wolfssl)
Sergei Golubchik
bugfix: incorrect plugin retry on COM_CHANGE_USER

as soon as the server sends any packet, plugin should not
work in mysql_change_user mode anymore, and should use the data
that the server sends, not the old mysql->scramble_buf
Sergei Golubchik
bugfix: correct plugin version comparison

(also fix it in C/C to use the same pattern)

The pattern is always the same now everywhere:

  if plugin_version < min_supported_api_version or
    plugin_version > current_api_version
  then it's an error.

in almost all cases min_supported_api_version is
`~0xFF & current_api_version`, meaning, major api
version must be the same.

Authentication API supports plugins of lower major version,
this support was specially implemented though.
Sergei Golubchik
.gitignore <- /build*/
Sergei Golubchik
MDEV-40445 TLS session resumption

in fact it was already on in the server, so this only
enables statistics to see it in SHOW STATUS, adds tests,
and updates C/C to match.

Assisted-By: Claude:claude-5-opus
Arcadiy Ivanov
fixup! MDEV-40802 COUNT(DISTINCT <blob>) fails when its tmp table converts

`heap.count_distinct_blob_convert` asserts that four of its aggregates
converted their in-memory temporary table to the on-disk engine. On a
32-bit build three of those assertions read `OFF`: the table never
filled up, so nothing was converted and those cases covered nothing.

The test filled a 64 KB in-memory table with 2000 rows, 1000 of them
distinct. On a 64-bit build the table overflows somewhere between 1500
and 2000 rows, so 2000 clears the limit by only a few hundred. A record
holding a blob carries a pointer to the value, and where a pointer is
four bytes rather than eight the record is narrower, so the same 2000
rows no longer reach the limit.

Use 8000 rows, 4000 distinct, several times what the wider build needs.

Only the count is chosen that way. The value widths and the memory
limit stay exactly where they were measured, because they are what
makes the table run out of record slots rather than out of blob space.
That is the case where the row left over at the overflow is a duplicate
of one already copied, and handling that duplicate is what the fix is
about; a limit or a width that overflows on a blob value converts the
table while covering nothing. Rows added past the overflow cannot move
it - it happens once the table is full, whatever follows - so the
64-bit build converts on the same row as before.

Verified by backing the fix out. With `ignore_last_dupp_key_error`
returned to 0 the test fails with `ER_DUP_UNIQUE` at 8000 rows exactly
as it did at 2000. No recorded `CONVERTED` line changed; only the row
and distinct counts did.
Vladislav Vaintroub
MDEV-40608 propagate DBUG_OFF, ENABLED_DEBUG_SYNC and SAFE_MUTEX to external plugins

they affect ABI, but aren't in headers, so must be passed separately
Sergei Golubchik
test that counts number of roundtrips in various situations

wolfssl needs curve25519 support, otherwise openssl client offers it,
server disagrees and they agree on something else after an extra roundtrip
Sergei Golubchik
MDEV-12320 change initial authentication plugin in the server

instead of unconditionally starting the authentication from the
mysql_native_password, start it from the plugin that will most likely
avoid "change plugin" packet (and thus a round-trip). Maintain counters
of what plugins were used to authenticate users so far, pick the plugin
with the largest count for new connections. But don't count plugins,
such as unix_socket, that need no client counterpart, they can never
cause a round-trip. And don't count pam that sends the password in
plain-text, we don't want to ask that of every new connection.

Uses separately allocated hash and memory, not acl_memroot, not
plugin->locks_count. This allows not to take extra acl_cache->lock
in acl_authenticate(), reset stats on FLUSH PRIVILEGES, and adapt to
the changed load with exponentially decreasing weights.

New status variable Connection_init_auth to show what plugin
is sent in the handshake packet.

Total number of MariaDB authentication plugins in the world is likely
below 15, including purely test plugins. So the hash is expected to
have hardly more than 3-5 entries.
Sergei Golubchik
MDEV-34846 PARSEC authentication improvement

parsec protocol allows to skip salt request - the client
can send the signed responce without asking for salt

now parsec + ssl takes only 2 roundtrips. Same as mysql_native_password
with no ssl.
Sergei Golubchik
MDEV-40445 TLS 1.3 early data (0-RTT)

Read client reply packet that was sent as TLS 1.3 early data
Send next server packet (e.g. OK or "change plugin" packet)
as early data to the client. This allows to establish a TLS
connection with no additional roundtrips.

Assisted-By: Claude:claude-5-opus
Vladislav Vaintroub
MDEV-40608 propagate DBUG_OFF, ENABLED_DEBUG_SYNC and SAFE_MUTEX to external plugins

they affect ABI, but aren't in headers, so must be passed separately