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
Aleksey Midenkov
MDEV-25529 Extract vers_set_starts() and add starts_clause

Extracts the STARTS-alignment logic into vers_set_starts() so it can be
reused. If the aligned local time falls into a DST gap (e.g. a midnight
that does not exist), the start of the gap is used instead of failing
with ER_PART_WRONG_VALUE. If it falls before the TIMESTAMP range (e.g. a
local midnight before 1970), the timestamp itself is used.

Also adds the Vers_part_info::starts_clause flag in preparation for later
commits. The flag records whether STARTS was given explicitly (by the
user or in the unpacked frm), which the auto-partitioning code uses to
decide whether to re-align STARTS to the existing history.
Vladislav Vaintroub
MDEV-41508 Off-by-One Uninitialized Stack Read in Proxy Protocol V2 Port Parsing

parse_v2_header() required only 25 (TCPv4) / 49 (TCPv6) bytes, but then
reads the source port at hdr[25] / hdr[49]. A truncated header could make
it read uninitialized stack.

Require the whole address block: 28 bytes for TCPv4 and 52 for TCPv6.

Also, the declared header length was trusted without checking how many
bytes vio_read() returned. Reject short reads. The spec requires the
sender to send the header at once, and allows the receiver to drop the
connection on a partial header, so a single vio_read() is enough.

The v1 parser had the same problem on EOF: vio_read() returning 0 was
taken for a byte read, so the uninitialized buffer was parsed. Require
exactly one byte.

Extend test_proxy_header_tcp with a header cut short before the port,
and add test_proxy_header_short for a header shorter than it declares.

Light refactoring, to avoid repetition: added connect_tcp() and
init_v2_header(), which are now shared by several proxy header tests.

Assisted-by: Claude:claude-sonnet-5-5
Aleksey Midenkov
MDEV-39306 Index corruption on UPDATE when using versioned InnoDB table (on UPDATE)

Duplicated by:

MDEV-39711 UPDATE on System-Versioned Tables uses the after-value
          in Row-format Binlog's before-image
MDEV-24646 ASAN heap-use-after-free in Field_blob::pack /
          THD::binlog_update_row upon DML on table with virtual column

vers_update_fields() recomputed virtual fields with VCOL_UPDATE_FOR_READ.
For BLOB virtual columns this mode swaps Field_blob::value and read_value
after the computation (swap_values). After the swap, Field_blob::value
holds the buffer that record[1] (the old row image) points to. The next
VCOL_UPDATE_FOR_WRITE computation, done by fill_record() right after
vers_update_fields(), stores the new value into that buffer and so
overwrites the old row image. As a result, InnoDB builds the old
secondary-index key from the new value (MDEV-39306), and the row-format
binlog writes the new value into the before-image (MDEV-39711), or a
freed buffer (MDEV-24646).

REPLACE had the same problem with record[2]. TABLE::delete_row() saves
the new row into record[2] and turns the conflicting row into history
with vers_update_end(). Its VCOL_UPDATE_FOR_WRITE computation stored
the virtual blobs of the old row into Field_blob::value, the buffer
record[2] points to, so the new row was written with them. Versioned
DELETE had the same problem with record[1]: it points to the virtual
blobs computed with VCOL_UPDATE_FOR_DELETE in Field_blob::value, and
vers_update_end() overwrote them.

The fix removes the virtual fields computation from vers_update_fields().
Its callers now recompute virtual fields with VCOL_UPDATE_FOR_WRITE after
the system fields are set: fill_record() already did, while
update_generated_fields(), multi_update::do_updates() and the RBR
appliers do it explicitly. multi_update::do_updates() still computes
virtual fields twice per row: before BEFORE triggers and after
vers_update_fields(), since the system fields cannot be set before
compare_record(). For REPLACE and DELETE, TABLE::delete_row() keeps
Field_blob::value with remember_blob_values() while it updates the
history row.

In the RBR appliers no VCOL_UPDATE_FOR_WRITE followed
vers_update_fields(), so record[1] was not overwritten there. Their
explicit computation replaces the one removed from vers_update_fields().
It uses VCOL_UPDATE_FOR_WRITE, so an error in a virtual field
expression now stops the slave instead of being turned into a warning.

In the Update_rows applier, vers_insert_history_row() also overwrote
the error of ha_update_row(), so a failed update on the slave was
ignored and its history row was written. The history row is now
written only when the update succeeded.

Based on the MDEV-24646/MDEV-39711 fix by ParadoxV5 (0c2f3de1a2d),
which switched vers_update_fields() to VCOL_UPDATE_FOR_WRITE.
Rex Johnston
MDEV-40012 PQ: refuse a plan that reads a table through a join buffer

A worker has no join buffer. setup_worker_jointabs() stripped the cache off
every worker JOIN_TAB and joined a row at a time, and pwt_table_conds() handed
the worker the buffer's scan filter (cache_select->cond) as well as
select_cond so that it still rejected what the buffer would have. The result
was correct, but it was not the plan the optimizer costed: BNL, BNLH, BKA and
BKAH were chosen for scanning the inner table once per buffer-full of outer
rows, and a worker scanned it once per driving row instead. For a full-scan
inner table that can be orders of magnitude more work than the serial plan
the parallel one is meant to beat.

So the gate now refuses the plan, as the prototype did. The check sits in
can_run_query_in_workers() beside the other per-table refusals, on
tab->cache or tab->use_join_cache, both of which make_join_readinfo() has
settled by the time parallel_join_check() runs in optimize_stage2(). The
refusal names the table in the optimizer trace.

The worker-side handling of a buffered table goes with it:

- pwt_table_conds() becomes pwt_table_cond() and no longer reports
  cache_select->cond. remove_redundant_bnl_scan_conds() only moves conjuncts
  out of select_cond when the tab has a BNL/BNLH cache, so for every tab the
  gate now admits select_cond is already the whole condition. A tab can still
  carry a cache_select without a buffer, because make_join_select() builds the
  scan filter before check_join_cache_usage() decides, but its cond is then a
  copy of conjuncts select_cond still holds. The gate no longer asks whether
  that copy is worker-safe, since no worker evaluates it.
- pwt_clone_table_conds(), which cloned both halves and ANDed them, is gone;
  setup_worker_jointabs() clones the one condition with pwt_clone_rebind().
- setup_worker_jointabs() no longer clears cache, use_join_cache and
  jbuf_tracker on the worker's copy; pwt_assert_tab_inert() asserts that the
  manager's tab has no cache instead. The copy's cache_select is still
  cleared, as it can be set on a tab without a buffer.

parallel_query_join: the BNL case now expects the query to run serially,
with the same rows. The full-scan inner table case (d3) was getting a BNL
buffer under the default join_cache_level, so it runs with
join_cache_level=0 to keep covering a worker-side full scan of an inner
table. parallel_query_why pins the new refusal reason.

This commit was prepared with Claude Code (Opus 5.5), which wrote the gate
check, removed the worker-side join buffer handling it made unreachable,
updated the tests, reconfigured and rebuilt the worktree, and ran the
parallel_query tests in normal and --ps-protocol modes.
Oleksandr Byelkin
MDEV-23864 BULK insert wrong values when command contain inner select

Optimized array binding INSERT/REPLACE executes the statement once,
so subqueries are executed with the first set of parameters only.

Execute INSERT/REPLACE, where a subquery result depends on parameters,
for every set of parameters (as not optimized array binding commands
are executed), keeping the first generated id as the result of the
whole operation.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Aleksey Midenkov
MDEV-40799 Runtime plugin/UDF load errors lost under --silent-startup

Regression from MDEV-32745 (7828fb475b0), which guarded the
plugin-load my_error() calls with opt_silent_startup.  That option is
a lifetime global, set once at startup and never reset, so the guard
suppressed the SQL error for the whole server lifetime, not just
during startup.  Runtime operations (INSTALL PLUGIN, CREATE FUNCTION
... SONAME) then skipped my_error(), never set the diagnostics area
and wrongly succeeded - e.g. main.ps's "call proc_1()" no longer
failed with ER_CANT_OPEN_LIBRARY.

Startup callers pass MYF(ME_ERROR_LOG); runtime callers pass MYF(0).
Gate the silencing on that flag via silent_plugin_startup() so it
applies only to the startup error-log path, and runtime errors always
reach the client.

No new test case: the runtime failure path is already covered by
existing tests (e.g. main.ps's ER_CANT_OPEN_LIBRARY check).  The
regression stayed invisible only because stock MTR does not start
servers with --silent-startup.  A dedicated test would have to restart
the server with --silent-startup solely to assert that a startup-only
option does not affect runtime, which adds little over the restored
invariant.
Aleksey Midenkov
MDEV-25529 Replace COMBINE macro with interval2sec() and interval2usec()

Turn the COMBINE macro of date_add_interval() into inline functions with
overloads for INTERVAL and MYSQL_TIME, so that the history partition
sizing can reuse the seconds part. Move the "Daynumber" comment that sat
above the macro to MAX_DAY_NUMBER, which it describes.

MAX_DAY_NUMBER moves to sql_time.h as a constexpr (the unused copy in
item_timefunc.cc is removed), and the range check of date_add_interval()
becomes the inline interval_days_out_of_range(), so that the sizing can
check that the seconds do not overflow.
Vladislav Vaintroub
MDEV-41508 Off-by-One Uninitialized Stack Read in Proxy Protocol V2 Port Parsing

parse_v2_header() required only 25 (TCPv4) / 49 (TCPv6) bytes, but then
reads the source port at hdr[25] / hdr[49]. A truncated header could make
it read uninitialized stack.

Require the whole address block: 28 bytes for TCPv4 and 52 for TCPv6.

Also, the declared header length was trusted without checking how many
bytes vio_read() returned. Reject short reads. The spec requires the
sender to send the header at once, and allows the receiver to drop the
connection on a partial header, so a single vio_read() is enough.

The v1 parser had the same problem on EOF: vio_read() returning 0 was
taken for a byte read, so the uninitialized buffer was parsed. Require
exactly one byte.

Extend test_proxy_header_tcp with a header cut short before the port,
and add test_proxy_header_short for a header shorter than it declares.

Light refactoring, to avoid repetition: added connect_tcp() and
init_v2_header(), which are now shared by several proxy header tests.

Assisted-by: Claude:claude-sonnet-5-5
Aleksey Midenkov
MDEV-25529 Comment on partition_element allocation in set_up_default_partitions()

partition_element and its push_back() list node are allocated on the
active arena (thd->mem_root). The allocation failure is already reported
there (by my_malloc() or the arena's error handler), which is why the
failure path only returns the error.
Aleksey Midenkov
MDEV-25529 Fix to_string() OOM return semantics for temporal types

Temporal_hybrid::to_string() returned the destination String with a stale
length when String::alloc() failed, instead of signalling the failure. Return
NULL on allocation failure.

Apply the same fix to the sibling to_string() methods of Interval_DDhhmmssff,
Time, Datetime and Date, which shared the identical flaw, so all five report
OOM consistently.

The val_str() implementations that return the to_string() result
directly did not set null_value, so a NULL result would be taken for a
value. They now go through the new Item::check_str(), which sets
null_value from the result, so a NULL does not stick to the item for the
next execution. The time_to_string_oom debug keyword makes
Time::to_string() fail as on out of memory.
Aleksey Midenkov
MDEV-25529 Auto-create: Pre-existing historical data is not partitioned as specified by ALTER

Adds logic into prep_alter_part_table() for INTERVAL ... AUTO to read the
history range (vers_get_history_range()) and size the new layout from it.
STARTS is aligned to the oldest history row_end (vers_set_starts()) if it
was not specified by the user, or if the user specified it later than
that; in the latter case WARN_VERS_WRONG_STARTS is printed. The history
partitions cover both the newest history row_end and the query time, so
DML after the ALTER does not need to create partitions. A PARTITIONS N
clause is kept if it gives more partitions. An explicit subpartition list
is replaced by as many default subpartitions. If the layout needs more
than MAX_PARTITIONS partitions (counting subpartitions), or the INTERVAL
is longer than the date range, the ALTER fails with
ER_VERS_HIST_PART_FAILED. Month and year intervals are stepped in UTC; a
boundary past the maximum TIMESTAMP ends a partial last history
partition.

In case of fast ALTER TABLE, f.ex. when partitioning already exists, the
above logic is ignored unless FORCE clause is specified. When user
specifies partition list explicitly the above logic is ignored even with
FORCE clause. With FORCE on a table already partitioned by SYSTEM_TIME
the work is done on a clone of the partitioning info. The parsed settings
are copied into the clone's vers_info with
Vers_part_info::copy_settings(), which keeps the clone's now_part and
hist_part (the parsed ones are NULL).

Before reading the range, the metadata lock is upgraded to
MDL_SHARED_NO_WRITE, and then the copy does not run online, so DML
cannot add history that would not fit the computed layout until the
ALTER ends (Alter_info::vers_history_read). LOCK=NONE and
ALGORITHM=INPLACE/NOCOPY/INSTANT are rejected for such ALTER before the
upgrade, so they do not wait for concurrent DML; the ALGORITHM error is
reported by the new Alter_info::report_algorithm_unsupported(), which
mysql_alter_table() uses as well. The range is read under a table lock
taken with mysql_lock_tables(), as some engines (Aria) set up their
transaction in THR_LOCK callbacks. The scan can be killed.

WARN_VERS_STARTS_BEYOND_INTERVAL for ALTER .. AUTO is checked after the
alignment, so it is not printed when STARTS was re-aligned to the history.

vers_get_history_range() detects if an index can be used for row_end
min/max stats and if so it gets it with ha_index_first() and
HA_READ_BEFORE_KEY (as it must ignore current data). Otherwise it does a
table scan to read the stats. Current rows with the pre-11.5 row_end
marker are not taken for history either. In the index they sort below
history after 2038, so the minimum is read after them. There is
test_mdev-25529 debug keyword to check the both and compare results.
WARN_VERS_SLOW_ROW_END is printed if the scan read any row and there is no
usable index (a disabled one does not count as missing).

Static key_cmp was renamed to key_eq to resolve compilation after key.h
was included as key_cmp was already declared there.
Aleksey Midenkov
MDEV-25529 ErrConvTimestamp for printing timestamps

Add ErrConvTimestamp, an ErrConv that formats a Timestamp in the session
time zone into a fixed buffer for warning and error messages, with the
number of fractional digits given explicitly. ErrBuff::set_mysql_time()
gets an optional digits argument for it. Use it when printing STARTS and
the history range.

Rename the error symbol ER_PART_STARTS_BEYOND_INTERVAL to
WARN_VERS_STARTS_BEYOND_INTERVAL and extend its message: it now takes the
STARTS timestamp and the query timestamp as arguments, so the number of
format arguments changes from one to three. The error number (4164) is
unchanged, so this is not an ABI break, but libmariadb still exports the
old symbol name for that number until its submodule is updated.
Nikita Malyavin
MDEV-41238 Bound parameters prevent condition pushdown into a grouped view, while CAST enables it

Item_param::deep_copy() returned NULL, and every rewrite that copies a
condition gives up on what it cannot copy. A conjunct holding a
prepared statement parameter was therefore left out of:
- pushdown into derived tables and views, including UNION branches
  and conditions on a derived table column whose expression holds a
  parameter;
- pushdown from HAVING to WHERE, for the part extracted from an OR;
- pushdown into a materialized IN subquery;
- split materialization, which then found no split key;
- the UPPER(col) IN (...) rewrite, which has skipped such conjuncts
  since MDEV-40006.
When the derived table groups by an expression with a parameter, the
condition was pushed into its HAVING instead of its WHERE.

CAST(? AS ...) worked around this only by accident. Before the
pushdown, constant propagation can replace a constant expression on
one side of = with a literal. A bare parameter already counts as a
basic constant, so propagation leaves it as it is. A client whose
charset differs from the column's got the pushdown by a similar
accident: the charset conversion replaces the parameter with a
literal.

Item_param::shallow_copy() now copies a parameter that holds a value,
NULL included, and deep_copy() calls it, as in other leaf items. Every
such copy is made while optimizing during EXECUTE, and the next
EXECUTE makes a new one, so a copy always carries the value bound for
the EXECUTE that made it. A copy that outlived its EXECUTE would keep
an old value and point into a string buffer that the next binding may
free, so shallow_copy() asserts that it does not allocate on the
statement's arena.

The copy is not added to m_clones. m_clones keeps the parameter's
copies in CTE definitions updated with each new value for the whole
life of the statement, while this copy lives for one execution.

An unbound parameter, DEFAULT and IGNORE still have no copy.

mysqltest gets a PS_bind_long command. It binds like PS_bind, but
sends every value with mysql_stmt_send_long_data() before each
PS_execute, so a test can reach a parameter in LONG_DATA_VALUE state.
Vladislav Vaintroub
MDEV-40287 Use MD5_CTX directly for MD5, like the SHA wrappers

my_md5() faked an EVP_MD_CTX in a stack buffer sized by EVP_MD_CTX_SIZE,
the same opaque-context hack MyCTX used for the cipher - and its only
guard, check_openssl_compatibility(), was removed earlier in this patch.

MD5_CTX is a concrete struct in both OpenSSL and WolfSSL (WolfSSL provides
the OpenSSL-compatible MD5 API), so use it directly with
MD5_Init/Update/Final on a caller-owned context, exactly like my_sha.inl
does for SHA*_CTX. This removes the EVP_MD_CTX hack and EVP_MD_CTX_SIZE;
my_md5_context_size() now returns sizeof(MD5_CTX).

SHA is unaffected - it already used concrete SHA*_CTX structs.

Assisted-by: Claude:claude-opus-4-8
Rex Johnston
MDEV-40012 PQ: refuse DEFAULT() in an expression a worker evaluates

DEFAULT(col) was evaluated against the worker's row. Item_default_value is
an Item_field whose field is a private copy made by make_default_field(),
reading the share's default_values but carrying the column's own table.
Its deep_copy() shares that field, and Pwt_field_rebinder, seeing the
manager's table, repointed it at the worker's real column. DEFAULT(col)
then read the current row's value, and for an expression default
calculate() called set_default() and wrote the default into the row --
wrong results in a release build, a marked_for_write assertion in a debug
one. It shows only when the expression also names the column some other
way; alone, DEFAULT(col) is table-independent and evaluated at optimize
time.

The gate now refuses any expression holding a DEFAULT(), found with
check_func_default_processor, which only Item_default_value answers.

Found by running the main suite with --parallel-worker-threads=4, where
type_temporal_innodb lost a "Truncated incorrect BOOLEAN value" warning
because the worker's WHERE evaluated to NULL. parallel_query_clone covers
a lost row, a gained row and the expression default that crashed, and
checks that none of them ran in the workers.

This commit was prepared with Claude Code (Opus 5.5), which traced the
wrong result from a main-suite sweep with workers enabled to the rebinder,
wrote the gate refusal and the test, confirmed the test fails without the
refusal, and ran the parallel_query tests in normal and --ps-protocol
modes.
Aleksey Midenkov
MDEV-39306 Comments on update_virtual_fields() modes
Aleksey Midenkov
MDEV-41493 AFTER UPDATE trigger reads wrong NEW value of BLOB/TEXT virtual column in system-versioned table

UPDATE, multi-table UPDATE, INSERT ... ON DUPLICATE KEY UPDATE and the
RBR Update_rows applier save the new row into record[2] and call
vers_insert_history_row(), which restores the old row from record[1]
and sets its row_end with vers_update_end(). The
VCOL_UPDATE_FOR_WRITE computation in vers_update_end() stored the
virtual blobs of the old row into Field_blob::value, the buffer that
the virtual blobs of the new row in record[2] point to, or reallocated
it when the old value was longer. After restore_record(record[2]),
AFTER UPDATE triggers read the old value, or freed memory, as NEW.

remember_blob_values() and restore_blob_values() skipped virtual
GEOMETRY columns: they selected MYSQL_TYPE_BLOB, while
virtual_not_stored_blob_fields counts all fields with BLOB_FLAG. So
GEOMETRY had the same problem also in TABLE::delete_row() for REPLACE
and DELETE.

UPDATE ... FOR PORTION OF has the same problem in period_make_insert(),
which computes the inserted leftover rows into record[0] while the new
row is saved in record[2], also for tables without system versioning.

The fix saves Field_blob::value with remember_blob_values() before the
history row in vers_insert_history_row() or the leftover row in
period_make_insert() is computed, and puts it back with
restore_blob_values() afterwards, as TABLE::delete_row() already does
for REPLACE (for DELETE it frees the saved values instead).
remember_blob_values() and restore_blob_values() now select fields by
BLOB_FLAG.
Rex Johnston
MDEV-40012 PQ: a worker must not end its container's scan after handing it over

A pre-aggregating worker accumulates into its own container, a temporary
table, and hands that container to the manager with sink->flush(). Just
before the hand-off, execute_and_handoff() closes the container's index,
because the manager starts its own scan of the container immediately after
the hand-off.

The tidy-up at exec_exit then closed the container a second time. Its
comment said this was only for the paths that reach exec_exit without a
hand-off, but the code ran on every path, including the path with a
successful hand-off. By that point the container may already belong to the
manager: claim_next_result() has seen it marked as done, and
pwt_tmp_table_source::next_row() has rebound it to the manager's thread and
called ha_rnd_init() on it. The worker's ha_index_or_rnd_end() therefore
ends the manager's scan. The manager's next ha_rnd_next() then fails
"inited == RND" in a debug build, and in a release build it reads from a
table with a closed scan.

The window between the flush and the second close is only a few
instructions wide, so the failure is rare: main.ps_3innodb hit it once in a
main-suite run with --parallel-worker-threads=4, on
SELECT ... UNION DISTINCT SELECT sum(a) ... GROUP BY b. The second close now
runs only on the paths that skipped the first close, as recorded by a flag
set next to the first one.

parallel_query_handoff makes the race certain. With pwt_handoff_sync set, a
worker that has handed over its container waits until the manager reaches
the new sync point pwt_manager_scanning_container, and the manager keeps its
scan open until the worker has finished its tidy-up. The test uses one
worker, so there is a single container and no risk of crossed signals. It
checks the rows and that the query ran in the worker. Without the fix it
fails the inited == RND assertion on every run.

This commit was prepared with Claude Code (Opus 5.5), which found the crash
in a main-suite sweep with workers enabled, traced it from the core dump to
the second close, wrote the fix, the sync points and the test, and confirmed
that the test fails without the fix in five runs out of five and passes with
it in normal and --ps-protocol modes.
Aleksey Midenkov
MDEV-25529 ALTER TABLE FORCE syntax improved

Lets an alter_list be supplied alongside a partitioning or
remove_partitioning clause in either order. This allows the FORCE clause
to be added to an existing command, and lets AUTO and FORCE be specified
together, which later commits of this task rely on.
Georg Richter
CONC-i860: Add STMT_ATTR_CB_RESULT_EX, a result callback with an end pointer

STMT_ATTR_CB_RESULT passes only the current position in the row to the
callback, so a callback cannot check that the data it parses is inside
the row. Add STMT_ATTR_CB_RESULT_EX with the signature

  int callback(void *data, unsigned int column, unsigned char **row,
              const unsigned char *end);

row points to the column's data (the length prefix for variable length
types) and is NULL for NULL values. end is the first byte after the
column's data. The callback must advance *row exactly to end. A non-zero
return value, or a *row different from end, aborts the fetch with
CR_MALFORMED_PACKET.

The legacy and the new callback share storage in MYSQL_STMT (union),
selected by a private type tag, so setting one replaces the other.
Passing NULL clears the callback and returns to the bind_result path.

mysql_stmt_attr_get() supports STMT_ATTR_CB_RESULT, STMT_ATTR_CB_RESULT_EX
and STMT_ATTR_CB_PARAM. The getter for the inactive result callback
returns NULL. Because older libraries reject unknown attributes,
mysql_stmt_attr_get(stmt, STMT_ATTR_CB_RESULT_EX, &cb) can be used to
detect support without side effects.
Sergei Petrunia
Just 'delete' the copy, don't cleanup it.
mariadb-PranavTiwari
MDEV-39343 mysql_upgrade tool now uses mariadb_info table and grant_tables_intact variable to decide whether upgrade is needed or not.
Vladislav Vaintroub
MDEV-41508 Off-by-One Uninitialized Stack Read in Proxy Protocol V2 Port Parsing

parse_v2_header() required only 25 (TCPv4) / 49 (TCPv6) bytes, but then
reads the source port at hdr[25] / hdr[49]. A truncated header could make
it read uninitialized stack.

Require the whole address block: 28 bytes for TCPv4 and 52 for TCPv6.

Also, the declared header length was trusted without checking how many
bytes vio_read() returned. Reject short reads. The spec requires the
sender to send the header at once, and allows the receiver to drop the
connection on a partial header, so a single vio_read() is enough.

The v1 parser had the same problem on EOF: vio_read() returning 0 was
taken for a byte read, so the uninitialized buffer was parsed. Require
exactly one byte.

Extend test_proxy_header_tcp with a header cut short before the port,
and add test_proxy_header_short for a header shorter than it declares.

Light refactoring, to avoid repetition: added connect_tcp() and
init_v2_header(), which are now shared by several proxy header tests.

Assisted-by: Claude:claude-sonnet-5-5
Sergei Petrunia
Add comments, Don't remove from THD::free_list.
Khaled Riyad
MDEV-41140 mariadb-server.postinst chown dereferences symlinks

The postinst, preinst, rpm postin and mysqld_safe scripts followed
symlinks when fixing ownership and permissions under /var/log/mysql
and /var/lib/mysql. Use -h and find -execdir, and let mysqld_safe
write as the mysql user, so symlinks there are no longer dereferenced.
Vladislav Vaintroub
MDEV-40287 AES_ENCRYPT() and KDF() return NULL with OpenSSL 4.0

MyCTX used an EVP_CIPHER_CTX in a stack buffer instead of allocating it
with EVP_CIPHER_CTX_new(). OpenSSL 4.0 rejects a context that was not
created that way for ciphers that use an IV: EVP_CipherInit_ex() fails
with "invalid iv length", so AES_ENCRYPT() and KDF() in CBC/CTR/GCM
modes return NULL. ECB has no IV and still works, which is why only the
non-ECB modes broke.

Fix:
Allocate the context with EVP_CIPHER_CTX_new()/EVP_CIPHER_CTX_free().
Also remove check_openssl_compatibility() and the EVP_CIPHER_CTX_SIZE
and EVP_CIPHER_CTX_init macros.

Verified against OpenSSL 4.0.1: the mysys aes-t test fails on the
CBC/CTR/GCM cases with the stack buffer and passes with
EVP_CIPHER_CTX_new().

No visible performance degradation: the extra allocation costs
~12 ns/call (WolfSSL) and ~30 ns (OpenSSL) on Windows at a 30-byte
payload, nothing at 16 KB, and nothing on Linux/glibc; sysbench OLTP
over encrypted tables and redo log is unchanged.

Tests main.tlsv13 and main.ssl_crl: OpenSSL 4.0.3 reports TLS alerts as
"tls alert ..." (was "ssl/tls alert ..."), accept both.

Assisted-by: Claude:claude-opus-4-8
Raghunandan Bhat
MDEV-34817: Performance schema is not cleared of removed package routines- part 2

Problem:
  1. Package routines were instrumented as "<package>.<routine>", the
  same key as a standalone routine named `<package>.<routine>`. Their
  statistics were merged, and DROP PACKAGE BODY dropped the statistics
  of standalone routines named `<package>.<anything>`.

  2. Routines of a package with a name of COL_OBJECT_NAME_SIZE bytes or
  longer were truncated to "<package>" and were never dropped.

Solution:
  Instrument package routines with internal object types
  OBJECT_TYPE_PACKAGE_PROCEDURE and OBJECT_TYPE_PACKAGE_FUNCTION. They
  are used only in the hash key: SETUP_OBJECTS lookups and OBJECT_TYPE
  columns still use the old PROCEDURE and FUNCTION. When a package body
  is dropped, drop only records with these types, including names
  truncated to the package name.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Nikita Malyavin
MDEV-41238 Bound parameters prevent condition pushdown into a grouped view, while CAST enables it

Item_param::deep_copy() returned NULL, and every rewrite that copies a
condition gives up on what it cannot copy. A conjunct holding a
prepared statement parameter was therefore left out of:
- pushdown into derived tables and views, including UNION branches
  and conditions on a derived table column whose expression holds a
  parameter;
- pushdown from HAVING to WHERE, for the part extracted from an OR;
- pushdown into a materialized IN subquery;
- split materialization, which then found no split key;
- the UPPER(col) IN (...) rewrite, which has skipped such conjuncts
  since MDEV-40006.
When the derived table groups by an expression with a parameter, the
condition was pushed into its HAVING instead of its WHERE.

CAST(? AS ...) worked around this only by accident. Before the
pushdown, constant propagation can replace a constant expression on
one side of = with a literal. A bare parameter already counts as a
basic constant, so propagation leaves it as it is. A client whose
charset differs from the column's got the pushdown by a similar
accident: the charset conversion replaces the parameter with a
literal.

Item_param::shallow_copy() now copies a parameter that holds a value,
NULL included, and deep_copy() calls it, as in other leaf items. Every
such copy is made while optimizing during EXECUTE, and the next
EXECUTE makes a new one, so a copy always carries the value bound for
the EXECUTE that made it. A copy that outlived its EXECUTE would keep
an old value and point into a string buffer that the next binding may
free, so shallow_copy() asserts that it does not allocate on the
statement's arena.

The copy is not added to m_clones. m_clones keeps the parameter's
copies in CTE definitions updated with each new value for the whole
life of the statement, while this copy lives for one execution.

An unbound parameter, DEFAULT and IGNORE still have no copy.

mysqltest gets a PS_bind_long command. It binds like PS_bind, but
sends every value with mysql_stmt_send_long_data() before each
PS_execute, so a test can reach a parameter in LONG_DATA_VALUE state.
Oleksandr Byelkin
MDEV-41502 Bulk INSERT: IN list and REGEXP with a parameter use the first parameter set for all rows

Optimized array binding INSERT/REPLACE fixes the items once, and parameters
are constants, so items cache values computed with the first set of
parameters (IN array, compiled REGEXP pattern).

Do not treat parameters as constants during optimized array binding
execution.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Oleksandr Byelkin
MDEV-34817 Performance schema is not cleared of removed package routines

Package routines are recorded in performance schema as
PROCEDURE/FUNCTION "<package>.<routine>", but dropping a package body
removed only the PACKAGE BODY record, so routine statistics survived
DROP PACKAGE [BODY] and DROP DATABASE.

Drop the routine records of the package together with the package body
record. Also reset statistics of a newly allocated PFS_program (as in
089caf901f9), so a reused slot does not inherit stale counters.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Aleksey Midenkov
MDEV-41617 Index corruption on UPDATE of table with long unique hash collision and indexed virtual blob

handler::check_duplicate_long_entry_key() reads all rows with the same
long unique hash. Each read (ha_index_read_map() and every
ha_index_next_same() hit) runs TABLE::update_virtual_fields() with
VCOL_UPDATE_FOR_READ, which swaps Field_blob::value and read_value of
virtual blob columns. The swap was undone only after the first read.
Every further read swapped again, so the buffer that record[1] (the old
row of UPDATE) points to ended up in value: the next read stored the
lookup row into it, or restore_blob_values() freed it. InnoDB then
built the old secondary index key from overwritten or freed memory and
did not find the record to update.

The fix undoes the swap after each read.
Mohammad Tafzeel Shams
MDEV-26123: Always mark spatial index reads as locking

A SELECT query could fail with ER_READ_ONLY_TRANSACTION when search
on spatial index is done after search on non-spatial index.

R-tree index search always require page locks. Previously,
trx->will_lock was only set for spatial indexes when the transaction
had not yet been started. If a transaction was already active due to
a prior non-spatial index access, index_read() for spatial index
returned HA_ERR_READ_ONLY_TRANSACTION instead of marking the transaction
as locking.

trx->will_lock must not be changed once a transaction has started, so
index_read() cannot safely fix this up by itself.

Fix:
- ha_innobase::external_lock() : Set trx->will_lock before the
transaction starts for any table with a spatial index
(dict_table_t::has_spatial_index()), so this no longer depends on
statement/index access order.
- ha_innobase::index_read() : Drop the HA_ERR_READ_ONLY_TRANSACTION
error; trx->will_lock is only set here as a pre-start fallback.
Aleksey Midenkov
MDEV-41621 Stored generated BLOB column is wrong on REPLACE into versioned table

REPLACE into a system-versioned table turns the conflicting row into
history in TABLE::delete_row(): it saves the new row into record[2],
restores the conflicting row from record[1] and sets its row_end with
vers_update_end(). The VCOL_UPDATE_FOR_WRITE computation in
vers_update_end() also recomputes stored generated columns. When
Field_blob::store() copies the value into Field_blob::value (e.g. on a
character set conversion), it overwrote the buffer that the stored
generated blob of the new row in record[2] points to, and the new row
was written with the value of the history row. The index on the column
holds the same value, so CHECK TABLE does not detect it.

remember_blob_values(), which keeps Field_blob::value around
vers_update_end() in TABLE::delete_row() and vers_insert_history_row()
and around update_generated_fields() in period_make_insert(), covered
only virtual columns.

The fix adds the with_stored parameter to remember_blob_values(),
restore_blob_values() and free_blob_values(), which also covers stored
generated blob columns. TABLE::delete_row(), vers_insert_history_row()
and period_make_insert() use it and size the storage by
TABLE_SHARE::virtual_fields, the number of all generated columns.
mariadb-PranavTiwari
MDEV-39343 Add Grant_tables_intact status variable

Add the status variable Grant_tables_intact. It is set on server startup
and on every FLUSH PRIVILEGES, and tells whether the grant tables in the
mysql schema (db, global_priv, roles_mapping, proxies_priv, tables_priv,
columns_priv, procs_priv) have the structure this server expects. This
makes it possible to detect that the tables restored from a dump of an
older version need mysql_upgrade.

A grant table that is missing, or that has a wrong structure, is written
to the error log and makes the variable OFF.

The expected structures are in the new sql/grant_table_defs.cc. They must
be updated together with scripts/mysql_system_tables.sql when a column of
one of these tables is changed.
Rex Johnston
PQ:  Code tidy ups and fixups of tidyups.

parallel_query_aggregate.test: fix statistics
impersonalize language in can_parallel_scan_jointab_access()
remove dead code (trace_parallel_scan_options)
add an assert to ensure we do not use Item_sum_avg without two packed fields
minor comment fixups
remove enum element NESTED_LOOP_DECLINED and use a local bool instead
fix silly code in pwt_row_layout::build_aggregates (throwing away a pointer
  then searching for it again later); 'one' starts as nullptr, because GCC
  cannot see that whole_table is unchanged between the two blocks and warns
  -Wmaybe-uninitialized in an ASAN build
remove pwt_batch* worker->manager streaming functions
tidy up Parallel_scan_partitioner::Scan_ctx::create_chunks to use
  create_chunks_unsplit

The 'one= nullptr' initialisation was added with Claude Code (Opus 5.5),
which traced the -Wmaybe-uninitialized warning to this change and folded
the fix in here.
Thirunarayanan Balathandayuthapani
MDEV-41550 MTR doesn't produce core dump or stack trace when a server hangs during startup

Problem:
========
If mariadbd does not finish starting up, for example it hangs in a
restart done by a test, mtr only killed it, so there was no way to
see where it was stuck.

Solution:
========
sleep_until_file_created(): when the wait for the pid file times out
and the process is still alive, send it SIGABRT and wait for it to
finish dumping core (MTR_DUMP_CORE_TIMEOUT seconds, default 120).
The server's fatal signal handler then writes the stack trace to the
error log, which mtr reports as usual.
Aleksey Midenkov
get_next_time() comment
Aleksey Midenkov
MDEV-25529 Auto-create: Pre-existing historical data is not partitioned as specified by ALTER

Adds logic into prep_alter_part_table() for INTERVAL ... AUTO to read the
history range (vers_get_history_range()) and size the new layout from it.
STARTS is aligned to the oldest history row_end (vers_set_starts()) if it
was not specified by the user, or if the user specified it later than
that; in the latter case WARN_VERS_WRONG_STARTS is printed. The history
partitions cover both the newest history row_end and the query time, so
DML after the ALTER does not need to create partitions. A PARTITIONS N
clause is kept if it gives more partitions. An explicit subpartition list
is replaced by as many default subpartitions. If the layout needs more
than MAX_PARTITIONS partitions (counting subpartitions), or the INTERVAL
is longer than the date range, the ALTER fails with
ER_VERS_HIST_PART_FAILED. Month and year intervals are stepped in UTC; a
boundary past the maximum TIMESTAMP ends a partial last history
partition.

In case of fast ALTER TABLE, f.ex. when partitioning already exists, the
above logic is ignored unless FORCE clause is specified. When user
specifies partition list explicitly the above logic is ignored even with
FORCE clause. With FORCE on a table already partitioned by SYSTEM_TIME
the work is done on a clone of the partitioning info. The parsed settings
are copied into the clone's vers_info with
Vers_part_info::copy_settings(), which keeps the clone's now_part and
hist_part (the parsed ones are NULL).

Before reading the range, the metadata lock is upgraded to
MDL_SHARED_NO_WRITE, and then the copy does not run online, so DML
cannot add history that would not fit the computed layout until the
ALTER ends (Alter_info::vers_history_read). LOCK=NONE and
ALGORITHM=INPLACE/NOCOPY/INSTANT are rejected for such ALTER before the
upgrade, so they do not wait for concurrent DML; the ALGORITHM error is
reported by the new Alter_info::report_algorithm_unsupported(), which
mysql_alter_table() uses as well. The range is read under a table lock
taken with mysql_lock_tables(), as some engines (Aria) set up their
transaction in THR_LOCK callbacks. The scan can be killed.

WARN_VERS_STARTS_BEYOND_INTERVAL for ALTER .. AUTO is checked after the
alignment, so it is not printed when STARTS was re-aligned to the history.

vers_get_history_range() detects if an index can be used for row_end
min/max stats and if so it gets it with ha_index_first() and
HA_READ_BEFORE_KEY (as it must ignore current data). Otherwise it does a
table scan to read the stats. Current rows with the pre-11.5 row_end
marker are not taken for history either. In the index they sort below
history after 2038, so the minimum is read after them. There is
test_mdev-25529 debug keyword to check the both and compare results.
WARN_VERS_SLOW_ROW_END is printed if the scan read any row and there is no
usable index (a disabled one does not count as missing).

Static key_cmp was renamed to key_eq to resolve compilation after key.h
was included as key_cmp was already declared there.
Rex Johnston
MDEV-40012 PQ: end a worker's parallel scan before close() frees m_prebuilt

A worker's handler was destroyed with its parallel scan still open.
exec() and scan_only() jump to their exit labels when
parallel_init_worker() fails, and the commonest failure is
HA_ERR_END_OF_FILE: no chunk was left for this worker, which happens all
the time on small tables. InnoDB has allocated m_pscan_worker by then, so
the scan was left for ~ha_innobase() -> parallel_scan_free(), which runs
after close() has freed m_prebuilt. parallel_scan_free() says it drops the
worker without end() for exactly that reason, but ~Parallel_scan_worker()
called end() anyway, and end() writes m_prebuilt->pscan_chunk_clamp: a
heap-use-after-free that ASAN reports on every such worker. In a build
without ASAN the write lands in freed memory, and it is the likely cause of
the intermittent SIGSEGV in free() inside pthread_create() when a later
worker reused a cached thread stack whose DTV had been overwritten.

Three changes close it. Both workers now end the scan at their exit label
instead of after the loop, so every way out ends it while the handler is
still open; ending a scan that was never begun is harmless.
ha_innobase::close() ends any worker or coordinator scan still open before
row_prebuilt_free(), so a path that misses it is ended properly rather than
left for the destructor; with the SQL layer put back as it was, this alone
keeps ASAN clean. And ~Parallel_scan_worker() no longer calls end(), so
parallel_scan_free(), now reached only for a handler destroyed without
close(), cannot write through m_prebuilt either. The close() hook and the
single call site are taken from a patch by a co-worker.

Two debug assertions state the rule the defect broke, so it no longer
takes ASAN to see it. ha_innobase::close() asserts that the SQL layer has
already ended a worker's scan, and parallel_scan_free() asserts that no
worker survives close(). The injection pwt_leave_scan_for_close makes the
SQL layer leave the scan of a worker that holds no cursor -- one handed no
chunk -- for close() to end, so the backstop is exercised on purpose and
the first assertion is told to allow it. pwt_end_worker_scan() holds the
injection and ends the scan at both exit labels.

parallel_query_no_chunk (debug only) runs a full scan and a range scan of a
one-page table with four workers, so three of them get no chunk, twice
each, through the normal and the scan-only path, with and without the
injection, checking the rows and that the workers ran every query. In a
plain debug build, removing the exit-label calls fails the first assertion
and removing the close() backstop fails the second.
~Parallel_scan_worker() no longer calling end() is defence for release
builds and is not reachable while the backstop is there.

This commit was prepared with Claude Code (Opus 5.5), which reproduced the
pthread_create() crash under ASAN and traced it, wrote the fix, folded in
the co-worker's close() hook and single call site, added the debug
assertions, the injection and the test, confirmed in a plain debug build
that the test fails with each of the two fixes removed, and ran the
parallel_query tests in normal and --ps-protocol modes and the main suite
with --parallel-worker-threads=4 under ASAN.