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-39596 Unexpected "No database selected" error for a CALL inside an anonymous block

For name1.name2() the PATH loop searches CURRENT_SCHEMA for a
package name1. In an anonymous block without a current database
resolve_current_schema() called copy_db_normalized(), which raised
ER_NO_DB_ERROR. But without a current schema there is no package
to find, name1.name2 can only be schema.routine.

Skip the unresolvable CURRENT_SCHEMA in an anonymous block, as it is
done outside of stored programs. Unqualified names never get here
without a current database, make_sp_name() and
Create_qfunc::create_func() reject them first.

Assisted-by: Claude:claude-opus-5-5
Marko Mäkelä
fixup! 2f9d9c14cb2550c95163e0a11fdb4a60732783d1

InnoDB_backup::archive_create(): Be PMEM agnostic
Alessandro Vetere
MDEV-41844 Test innodb.buf_pool_resize_pcur_restore is flaky

The procedure read_into_first_new_block() requires that the page of a
table is read into the first block that a buffer pool grow adds. While
history exists, the purge coordinator runs once per second. It reads
undo log pages, and purge_truncation_callback() reads the header page
of each rollback segment. On a slow builder, these reads can take the
first new block in all 5 attempts.

Stop purge for the duration of the test with FLUSH TABLES ... FOR
EXPORT on a separate connection. A read view does not stop these reads.
Remove the wait for purge between the two cases, because purge is
stopped.

purge_sys_t::stop() waits only for the purge coordinator. Runs of
purge_truncation_callback() that the coordinator submitted before can
still read pages after FLUSH TABLES ... FOR EXPORT returns. Add the
debug keyword purge_stop_wait_truncation, which makes
purge_sys_t::stop() also wait for these runs, and set it in the test.

Assisted-by: Claude:claude-opus-5-5
Sergei Golubchik
MDEV-40543 Assertion `opt->value.str' failed in append_create_options on ALTER TABLE ... REORGANIZE PARTITION

partition syntax is printed from the parsed option list, which
contains =DEFAULT (NULL value) entries. Skip them, like frm writer does.

Assisted-by: Claude:claude-opus-5-5
Vladislav Vaintroub
MDEV-38741 MYSQL_METHODS: the same layout in all builds

The members of MYSQL_METHODS after on_close_free existed only if
!MYSQL_SERVER or EMBEDDED_LIBRARY, so the client in the server
(sql-common/client.c, built with MYSQL_SERVER) had a shorter table than
libmysqld/libmysql.c, which mariabackup builds as a client.
mysql_next_result() read methods->next_result beyond the end of that
table, and crashed.

Declare all the members always, and set all of them in client_methods
and in local_methods, next_result of a client is read_query_result.
Revert the check of the embedded server in mysql_next_result(), it is
not needed now.

Assisted-By: Claude:claude-sonnet-5-5
Marko Mäkelä
MDEV-40935: Infinite loop in SET GLOBAL innodb_log_archive

log_t::set_archive(): Ensure that a log checkpoint will be written
even if no dirty pages exist in the buffer pool. This was reproduced
with an intermittent livelock during the test innodb.log_file_size_online.
Vladislav Vaintroub
MDEV-38741 perfschema.nesting: one more THD::LOCK_thd_data wait

dispatch_command() now takes THD::LOCK_thd_data around set_command() and
the check of the disconnect state, which makes the disconnect watcher
and the start of a statement agree. The test lists every instrumented
mutex wait of a statement, so each of its five statements has one more
wait/synch/mutex/sql/THD::LOCK_thd_data in the "starting" stage, and
the event ids after it are shifted by one.

Assisted-By: Claude:claude-sonnet-5-5
Jan Lindström
MDEV-40212 galera_duplicate_primary_value stalls on single applier

Failure:
- The test needs two write sets applied concurrently by two
  appliers on node 1, but never sets wsrep-slave-threads, so it
  defaults to 1.
- lock_wait_wsrep_kill's debug sync parks the applier thread
  itself on WAIT_FOR wsrep_after_kill_continue; with only one
  applier, the second write set has no free thread, and WAIT_FOR
  wsrep_after_kill_reached_2 blocks until --debug-sync-timeout
  (300s).
- The timeout does not fail the test, so it reports pass without
  ever reaching the concurrent brute-force state MDEV-31658 added
  it to cover.

Fix:
- Set wsrep-slave-threads=2 on node 1, so the two independent
  write sets apply concurrently, matching what the test is meant
  to exercise.

Assisted-by: Teemu Ollakka <[email protected]>
Assisted-by: https://mariadb.org/governance/governance-ai-policy/
bsrikanth-mariadb
MDEV-41748: Syntax error with BETWEEN in FederatedX pushdown

A multi-table UPDATE/DELETE pushed down to FederatedX failed on the
remote server with a syntax error when the WHERE clause had a BETWEEN
whose bounds are constants, e.g.

  UPDATE t1 AS a, t1 AS b SET a.x=NULL WHERE a.x BETWEEN NULL AND NULL;

The bounds are wrapped in Item_cache objects. The cache was not filled
yet when the query was printed, so Item_cache::print() emitted
"<cache>(NULL)", which is not valid SQL.

Fix: when printing with QT_PARSABLE, an Item_cache that holds no value
now prints the item it caches. A cache that already holds a value still
prints it as a literal. Output for callers that do not pass
QT_PARSABLE is unchanged. Only FederatedX pushdown uses QT_PARSABLE.

Add a test that pushes down multi-table SELECT, UPDATE and DELETE with
NULL and DATE BETWEEN bounds, and checks both the statements the remote
server received and the resulting data.
Vladislav Vaintroub
MDEV-38741 mariabackup: wait for the statements of the DDL thread

The debug thread of mariabackup that emulates a DDL sends several
statements in one query, and closed its connection as soon as the first
of them returned. The server used to run the rest, now the disconnect
watcher aborts the ALTER of a client that is gone, and
mariabackup.innodb_ddl_on_intermediate_table hangs, as the debug sync
signals the main thread waits for do not come. The thread reads the
results of all the statements before it closes the connection.

mysql_next_result() of libmysqld/libmysql.c, which mariabackup uses,
called methods->next_result. Only the methods of the embedded server
have it, the client in the server (sql-common/client.c, built with
MYSQL_SERVER) has a shorter table, and the call crashed. Outside of the
embedded server it calls read_query_result, which is next_result of the
client library.

Assisted-By: Claude:claude-sonnet-5-5
Marko Mäkelä
MDEV-41813 OPTIMIZE TABLE makes tables grow

PageBulk::init(): Add a parameter for a hint page number, so that we will
try to allocate adjacent pages for the index.

After this change, the innodb_alter_copy_bulk=ON code path may actually
produce smaller files than the legacy code path.

Based on mysql/mysql-server@a3e2dbddec48ec229484bdf082b8c2f34ccf8c0b

Reviewed by: Thirunarayanan Balathandayuthapani
bsrikanth-mariadb
MDEV-41749: FederatedX pushdown sends "<nop>(" in pushed-down SQL

A pushed-down statement containing "x < SOME (subquery)" failed on the
remote server with a syntax error, as the query text contained the
internal "<nop>(...)" wrapper. "x < ALL (subquery)" is wrapped in
"<not>(...)" in the same way.

Item_func_not_all::print() prints the wrapper whenever `show` is set.
Omit it when QT_PARSABLE is requested and print the wrapped predicate in
parentheses, as precedence() reports the wrapper, so a parent expression
would not add parentheses itself.

While the ALL/ANY subquery item is intact, args[0] prints the original
"x op ALL|ANY (subquery)". When the subquery is reduced to a constant
("x < ALL (SELECT 2)"), it is replaced by a comparison with the inverted
comparator, and the negation is held by "<not>". Print it explicitly as
"NOT (x >= 2)" for ALL, otherwise the remote server returns the
complementary rows. ANY/SOME needs no negation.

Item_allany_subselect::print() printed its left operand without
parentheses, so "(a XOR b) < ALL (subquery)" was pushed as
"a xor b < all (...)" and parsed by the remote server as
"a XOR (b < ALL ...)". Use print_parenthesised(), as
Item_in_subselect::print() does.

Add tests to federatedx_pushdown_upd_del covering SOME and ALL in
UPDATE, DELETE and SELECT, with results compared with pushdown OFF and
the pushed-down text checked in the remote general log.
Dave Gosselin
MDEV-40779:  Wrong result for DISTINCT with HAVING, ROLLUP

A query with DISTINCT over GROUP BY now reads AVG, STD and VARIANCE
values from the DISTINCT temp table, and its ROLLUP rows pass HAVING
while keeping NULL from the columns that they roll-up.

The following scenario now returns correct results of -1.0000:

  CREATE TABLE t1 (a INT);
  INSERT INTO t1 VALUES (1),(2),(3);
  SELECT DISTINCT -AVG(a) FROM t1 GROUP BY a HAVING AVG(a) < 2;

Only the group with average 1 passes HAVING and goes into the DISTINCT
temp table.  Duplicate removal checks HAVING again on that row, after
all three groups have been read.  The row now stores its own average,
and the second check reads 1 from it.  Before, the check read 3, the
average of the last group, and deleted the row.

This scenario now returns 2, 2:

  SELECT DISTINCT a, a+0 FROM t1 GROUP BY a WITH ROLLUP HAVING a=2;

The ROLLUP total row has a=NULL and fails HAVING.  For this query,
HAVING is on the JOIN_TAB of the grouping table while JOIN::having is
NULL.  rollup_write_data considers JOIN_TAB::having if available,
otherwise it falls back to JOIN::having which was set during
JOIN::prepare.  Without HAVING, SELECT DISTINCT a+0 over the same
groups returns 1, 2, 3 and NULL.  The ROLLUP row stores NULL for a
even though a is not in the select list, and a+0 is computed from that
stored a.

Assisted-By: Claude Opus 5.5 <[email protected]>
Jan Lindström
MDEV-31300 Suppress RESET SLAVE's Using_Gtid note across galera tests

RESET SLAVE/RESET SLAVE ALL emits a Note when it changes Using_Gtid,
and whether it does depends on the prior value, which can drift with
unrelated replication changes - galera_query_cache_invalidate,
galera_2_cluster and galera_gtid_2_cluster depend on it appearing.
Wrap every RESET SLAVE/RESET SLAVE ALL across the galera and
galera_3nodes suites in --disable_warnings/--enable_warnings, so no
test depends on that value.

Assisted-by: Claude:claude-4.6-sonnet
bsrikanth-mariadb
MDEV-41862: Syntax error with DELETE and IN in FederatedX pushdown

A single-table DELETE with an IN subquery on the same table, pushed
down to FederatedX, failed on the remote server with a syntax error:

  DELETE FROM t3 WHERE a IN (SELECT a FROM t3 WHERE a < 3);

The target table of a single-table DELETE is not in top_join_list, so
the list is empty. st_select_lex::print() printed it as
"delete  from  using dual where ...".

Fix: in st_select_lex::print() use top_join_list only when it is not
empty, and otherwise print the tables from leaf_tables. For DELETE that
branch emits the "from " keyword. UPDATE is unchanged.

As a side effect, EXPLAIN EXTENDED of a single-table DELETE no longer
shows the dummy "using dual" and prints "delete from `t` where ...".
A DELETE through a view now prints the view instead of its base table.
Update the affected results (explain_non_select,
myisam_explain_non_select_all, opt_hints, opt_hints_index, opt_trace,
versioning.delete_history).

Add a test to federatedx_pushdown_upd_del that pushes down DELETE (also
with ORDER BY ... LIMIT and as a prepared statement) and UPDATE with an
IN subquery on the same table, and checks both the resulting data and
the statements received by the remote server in its general log.
Vladislav Vaintroub
MDEV-38741 Abort the running statement when its client disconnects

The server notices that a client is gone only when it reads from or
writes to the socket. While a statement runs, for example waiting for
an InnoDB row lock, a disconnect goes unnoticed. The statement keeps
running, and its transaction keeps owning its locks, so other
connections wait for locks of a client that is not there.

Watch the client socket from the end of login until the connection
ends, or the client sends COM_QUIT, using only the events the OS
reports on its own. When the client has gone, abort the running
statement with the new KILL_DISCONNECT state, found by thread id like
KILL does, through a kill_thd_locked() that KILL shares. A quitting
connection ignores it.

KILL_DISCONNECT is statement-level, like KILL QUERY. When it does abort
a statement, the connection ends after the error of that statement is
sent, so the commands that the client pipelined behind it do not run.
A disconnect noticed while the connection is idle, before the statement
starts, is remembered and applied when the next statement starts.
The reason, "Client disconnected", is logged once, and a failed write
of the error is not reported. An orderly COM_QUIT, or a close without
it, is handled as before.

The global variable disconnect_watcher, ON by default, can be set OFF
to switch the watcher off. It affects the connections made after the
change, and exists only where there is a watcher: Linux, Windows and
the BSD systems, not the embedded server. If the watcher cannot start,
the variable is OFF and cannot be set ON.

Linux: an epoll set, polled by the connection accepting loop.
EPOLLRDHUP is requested, as a close and a crashed client can be only a
FIN.
Windows sockets: WSAEventSelect(FD_CLOSE), with a wait per socket on a
thread pool of our own that has a single thread, so the callbacks run
one by one.
Windows named pipes: the wait is for the exit of the local client
process. A client that only closes the pipe is not noticed.
BSD and macOS: kqueue, EV_EOF.
Replication dump threads are watched too.

mysqltest: dirty_close takes an optional "linger" (the default, a FIN)
or "nolinger" (SO_LINGER 0, so the server gets a reset) argument.

main.disconnect_lock_wait, disconnect_lock_owner (the client's
transaction is rolled back and its locks are released),
disconnect_wait_kinds (a CPU-bound statement and a table level lock
wait, which do not poll the connection themselves),
disconnect_lock_wait_idle (debug), disconnect_lock_wait_pipe (Windows)
and sys_vars.disconnect_watcher_basic are new. mysql_client_test gets
test_disconnect_pipelined: a client sends two commands in one write and
half-closes while the first waits for a lock. The connection ends, and
the second command is not run.
disconnect_lock_wait and disconnect_lock_wait_pipe run with the watcher
ON and OFF, the others need it ON. innodb.xa_recovery no longer needs
the shortened lock wait timeout with the watcher ON. rpl_ssl1 and
rpl_password_boundaries wait for the session of the slave to end
before DROP USER, max_binlog_total_size and rpl_fail_register kill the
dump thread only if it is still there, and rpl_get_lock suppresses the
read error of a reset connection on Windows (errno 10054).

Assisted-By: Claude:claude-sonnet-5-5
Sergei Golubchik
MDEV-40543 Assertion `opt->value.str' failed in append_create_options on ALTER TABLE ... REORGANIZE PARTITION

partition syntax is printed from the parsed option list, which
contains =DEFAULT (NULL value) entries. Skip them, like frm writer does.

Assisted-by: Claude:claude-5.5-opus
Jan Lindström
MDEV-31300 Suppress RESET SLAVE's Using_Gtid note across galera tests

RESET SLAVE/RESET SLAVE ALL emits a Note when it changes Using_Gtid,
and whether it does depends on the prior value, which can drift with
unrelated replication changes - galera_query_cache_invalidate,
galera_2_cluster and galera_gtid_2_cluster depend on it appearing.
Wrap every RESET SLAVE/RESET SLAVE ALL across the galera and
galera_3nodes suites in --disable_warnings/--enable_warnings, so no
test depends on that value.

Assisted-by: Claude:claude-4.6-sonnet
Khaled Riyad
MDEV-39001 Server crashes in find_type2 / Field_enum::store upon returning ENUM from function

Item_sp keeps sp_result_field, built from m_sp, across executions. A
trigger or prepared statement can outlive that sp_head, when CREATE
TEMPORARY TABLE flushes the routine cache or the function is replaced.
The next execution reused a field with a dangling ENUM TYPELIB or the
old return type. Regression of MDEV-26115.

Delete the field in Item_sp::cleanup() and build it from the current
m_sp on each execution. The field memory now belongs to the item, in
result_field_mem_root, so cleanup() can run at any time, which is what
MDEV-26115 needed.
Vladislav Vaintroub
mysql_client_test: accept a lost connection in test_proxy_header_tcp

When the server rejects a malformed proxy header, it closes the connection
with a part of the header still unread. The close can then be a TCP reset,
and the client may get CR_SERVER_LOST instead of the error that the server
sent. Accept both, the server side is checked by the error log.

Assisted-By: Claude:claude-sonnet-5-5
Vladislav Vaintroub
Do not attempt to resolve addresses that getnameinfo debug injections fake

this speeds up debug MTR on all systems that would attempt to resolve
addresses (and result is thrown away afterwards nonetheless)

Assisted-By: Claude:claude-sonnet-5-5
Sergei Golubchik
MDEV-41630 ST_IsValid crashes on polygons with many rings
Thirunarayanan Balathandayuthapani
MDEV-32067 InnoDB linear read-ahead had better be logical

The traditional linear read-ahead, enabled by
innodb_read_ahead_threshold, only helps when consecutively
accessed pages have adjacent page numbers.

That is rarely true for B-tree leaf pages: after splits,
merges and page reuse, the logical order of leaves has
nothing to do with their physical page numbers. So for a
scan of scattered leaves the old read-ahead either did nothing
or read the wrong pages.

This replaces linear read-ahead of index pages with a logical
read-ahead that prefetches the actual leaf pages a scan is
going to visit, discovered from the B-tree during traversal.
After this change, the exact nonzero value of
innodb_read_ahead_threshold matters only for the read-ahead of undo
log pages.

buf_read_ahead_undo(): Renamed from buf_read_ahead_linear(). Now
invoked only for undo log pages, whose page numbers are
sequential. It is no longer called on BLOB pages or index pages.

buf_read_ahead_one(): Read ahead a single leaf page.

buf_read_ahead_pages(): Read ahead a set of known B-tree leaf pages.
Stops at the first page that is already resident or fails.

buf_read_ahead_random(): Split into a decision part(which extent
to read) and action part (issue the reads)

The pending read throttle now compares the
os_aio_pending_reads_approx() with buf_pool.curr_size() instead of
current pool bytes, which had made the throttle a no-op.

buf_read_ahead_pages() releases the tablespace reference
on a failed/corrupted page.

btr_ra_latch: A caller-owned buffer of leaf page numbers, which also
records the level-1 page and the child page number of the last node
pointer collected, so read-ahead can be resumed later.

btr_ra_collect(): Scan the node pointers of PAGE_LEVEL=1 page from
the given record in scan order, appending child page numbers

btr_cur_t::search_leaf(), btr_cur_t::open_leaf(): At PAGE_LEVEL=1,
after the descent has located the child to follow, harvest leaf page
numbers starting at that child in the scan direction. These are
exactly the leaves the cursor will visit, so prefetch is precise
regardless of physical page numbers.

btr_ra_resume_rec(): Locate the record from which read-ahead should
resume, by re-finding the last harvested child by value. It walks only
genuine records, so a concurrent page reorganization cannot cause
an invalid read

btr_pcur_move_to_next_page(): Prefetch the sibling after the
one being entered, with buf_read_ahead_one().

btr_pcur_move_backward_from_page(): Add a no-wait fast path that
latches the previous page if it is resident
(validating it against the current page), and prefetch
the one before it.

btr_copy_blob_prefix(): Simplified; no longer relies on
FIL_PAGE_PREV/NEXT, which are not initialized on BLOB pages.

records_in_range() reports the first and last leaf page of a
range, and the optimizer forwards that extent to the engine through
handler::advise_page_range().

multi_range_read_info_const() takes a page_range parameter;
the default MRR implementation aggregates the
per-range extents, and opt_range.cc carries the result through the
chosen QUICK_RANGE_SELECT, whose reset() calls advise_page_range()
before multi_range_read_init(). The extent only sizes and bounds
read-ahead; it never gates the pages the scan actually reads.
The LIMIT is converted in multi_range_read_info_const() to a
leaf count (m_ra_limit_pages).

mrr_readahead_from_scan_range() derives the read-ahead ceiling
from the advised extent, falling back to that LIMIT-based estimate.
The ceiling is capped by init_readahead_window() at 1/256 of
the buffer pool (but at least 4 pages), because every prefetched
page occupies a buffer pool frame as soon as its read is issued.

The window ramps up: a positioning read harvests at most 4 leaves
and resets the window to 4, and readahead_refill() doubles it
toward the ceiling. Single-row probes such as index_first()
for MIN()/MAX(), and every range of a multi-range read,
therefore prefetch only a few leaves while long scans reach
full depth.

search_with_readahead(): Position the cursor while collecting the leaf
pages, then call start_readahead(). It is used by
index_read() and by the first general_fetch() only when
read-ahead is armed, so the other paths carry no read-ahead locals.

start_readahead(): For a just-positioned scan, prefetch the collected
batch and set up the rolling cursor.

readahead_refill(): As the scan advances (general_fetch()),
resume the level-1 harvest under an index S-latch, chain across
level-1 siblings, and prefetch the next batch bounded by the
advised last leaf, or to the end of the index when the extent
is unknown. The reads are issued after the latches are released.

Read-ahead is started from index_read() (range and full index scans),
rnd_init()/general_fetch() (full table scans), and, at one-page depth,
from row_merge_read_clustered_index() (OPTIMIZE/ALTER rebuild).
general_fetch() sets active_handler_stats before issuing
read-ahead so prefetched pages are attributed to the query.

dict_stats_analyze_index_for_n_prefix(): Choose the records to dive
below first, prefetch their child pages in one batch with
buf_read_ahead_pages(), and then dive below the same records.

Undo/system tablespace shrinking holds fil_space_t::latch exclusively
(x_lock_space()), and buf_read_ahead_undo() holds it shared,
via the new fil_space_t::s_lock_try() that never waits,
while it checks the size and issues the reads.
If the latch is not available, the read-ahead is skipped.

trx_undo_get_prev_rec(), trx_undo_get_prev_rec_from_prev_page():
take the trx_undo_t object instead of a long parameter list and
always latch shared.
Marko Mäkelä
MDEV-40935: Assertion os_file_set_size() failed during shutdown

os_file_set_size(): Consistently handle EINTR also after server
shutdown has been initiated. This will avoid a crash when an
innodb_log_archive=ON log file needs to be created or extended
during shutdown. This only affects the posix_fallocate(2) or
Linux fallocate(2) code path; if the file is being extended by
regular writes, we will retry in case of EINTR.

On Microsoft Windows, there is no clear documentation what would happen
if the process is being terminated while SetFilePointerEx() or
SetEndOfFile() are in progress.
Kristian Nielsen
Fix corrupt query string in mysqltest embedded

The mysqltest --send command re-used the same buffer for the query string to
send to each connection thread. This caused a race where a following --send
could overwrite the query before the first connection thread had time to
copy out the string, resulting in executing a corrupt query.

Instead, allocate a new string for each --send and free it in the connection
thread.

This was seen as a sporadic failure in --embeded-server in test
innodb.temp_truncate_freed_race (and probably others).

Signed-off-by: Kristian Nielsen <[email protected]>
Sergei Petrunia
Merge: MDEV-39226: Push whole multi-table update/delete down into engines
Marko Mäkelä
fixup! 2f9d9c14cb2550c95163e0a11fdb4a60732783d1

Fix InnoDB_backup::de_hardlink()
Vladislav Vaintroub
Do not attempt to resolve addresses that getnameinfo debug injections fake

this speeds up debug MTR on all systems that would attempt to resolve
addresses (and result is thrown away afterwards nonetheless)

The proxy header tests connect from the reserved 192.0.2.0/24, whose
lookup waits for a DNS timeout. Clean up the tests that did not use
getnameinfo_error_noname yet with a helper function skip_dns(), instead
of repeating the SET GLOBAL debug_dbug statements.

Assisted-By: Claude:claude-sonnet-5-5
Vladislav Vaintroub
MDEV-38741 mariabackup: wait for the statements of the DDL thread

The debug thread of mariabackup that emulates a DDL sends several
statements in one query, and closed its connection as soon as the first
of them returned. The server used to run the rest, now the disconnect
watcher aborts the ALTER of a client that is gone, and
mariabackup.innodb_ddl_on_intermediate_table hangs, as the debug sync
signals the main thread waits for do not come. The thread reads the
results of all the statements before it closes the connection.

mysql_next_result() of libmysqld/libmysql.c, which mariabackup uses,
called methods->next_result. Only the methods of the embedded server
have it, the client in the server (sql-common/client.c, built with
MYSQL_SERVER) has a shorter table, and the call crashed. Outside of the
embedded server it calls read_query_result, which is next_result of the
client library.

Assisted-By: Claude:claude-sonnet-5-5
Marko Mäkelä
MDEV-14992/MDEV-38362/MDEV-39092 BACKUP SERVER

The following SQL statements will be introduced:

BACKUP SERVER TO '/path/to/directory' [ 1 CONCURRENT ];
BACKUP SERVER WITH [ 1 CONCURRENT ] 'command';

In place of the 1, any positive number of threads may be specified.
For the first variant, '/path/to' must exist and '/path/to/directory'
must not exist; that is where the backup will be written to.

For BACKUP SERVER WITH, the string mariadb-backup-
will be prepended to the specified command and quoted.
Path separators or quotes are not allowed in command.
We expect the mariadb-backup-command to be in the
search PATH of the mariadbd process.
For now, we distribute no such script or program;
the user has to write one, something like this:

zstdmt|exec ssh [email protected] "exec cat > $1.tar.zstd"

The standard input of that command will be in a format compatible with
GNU tar --format=oldgnu (and also BSD tar variants that are also part of
Microsoft Windows and Apple macOS). The command is expected to optionally
compress and encrypt the stream and redirect it to a file on a local or
a remote server. The BACKUP SERVER WITH will append an additional argument,
a positive base-ten number in ASCII, starting with 1, to identify the
current thread. In this way, there can be multiple concurrent streams.
The InnoDB write-ahead log will be streamed near the end, because
the stream format requires the file sizes to be declared upfront.

In BACKUP SERVER TO ... 2 CONCURRENT or more, one thread will be
responsible for copying the InnoDB write-ahead log from the beginning
of the backup, running InnoDB_backup::log_track(). Other files will be
copied by other threads.

Note: In single-threaded BACKUP SERVER TO, the parameter
innodb_log_recovery_start that is written out to backup.cnf is
STRICTLY NECESSARY TO AVOID CORRUPTION during recovery! By default,
InnoDB crash recovery starts from the latest available log
checkpoint. However, for restoring a backup, recovery must start from
the checkpoint that was the latest when the backup was
started. Starting recovery from a possible later checkpoint will
result in a corrupted database!

The backup or the first stream will contain a file backup.cnf, which
includes parameters needed for restoring the backup. Currently,
these are innodb_log_recovery_start and innodb_log_recovery_target.
If innodb_log_recovery_target>0, InnoDB will be in read-only mode,
not allowing any writes to persistent files other than via the log
application.

To restore a streaming backup made with BACKUP SERVER WITH, an empty
directory needs to be created and all streams be extracted there using
the standard tar utility of the operating system, optionally after
undoing any encryption or compression that had been added by the
backup command. Then, the backup is prepared or MariaDB server started
up on the extracted directory, similar to as if the BACKUP SERVER TO
statement had been used.

The following will be implemented separately:

MDEV-39061 mariadb-backup compatible wrapper script for BACKUP SERVER
MDEV-40163 Partial backup and restore
MDEV-39091 Back up ENGINE=RocksDB
MDEV-40333 Concurrent DDL for Aria tables in BACKUP SERVER
MDEV-39987 Improve BACKUP SERVER performance for ENGINE=Aria

The implementation introduces a basic multi-threaded driver
Sql_cmd_backup, storage engine interfaces, and basic copying of the
storage engines InnoDB, Aria, MyISAM, MERGE (MyISAM), Archive, CSV.

backup_target: A structured data type to represent a target directory.
On Microsoft Windows, we must use directory paths because there is
no variant of CopyFileEx() that would work on file handles.

backup_sink: Wraps a per-thread output stream as well as storage
engine specific context. The member "id" identifies the thread,
between 0 and CONCURRENT-1.

backup_context: Process-wide BACKUP SERVER context.
Handles the directory traversal and copying of
files for built-in storage engines that do not
implement this backup interface.

backup_target_stage: Wrap backup_context.

backup_target_stage::step(),
backup_context::step(): Process a file
from a directory scan, or by invoking
handlerton::backup_step().

handlerton::backup_file(): Check if a file should be
included in the backup. Implemented for ENGINE=Aria
in maria_backup_file().

handlerton::backup_start(), handlerton::backup_end(): Invoked at the
start or end of a backup phase, in the thread that executes a
BACKUP SERVER statement.

handlerton::backup_step(): A backup step that can be invoked from
multiple threads concurrently, between the execution of the corresponding
handlerton::backup_start() and handlerton::backup_end() of the same
phase.

copy_entire_file(): A file copying service for POSIX systems.

clone_file_range(): A wrapper for Linux FICLONERANGE, which
may fail and thus require a fallback to backup::copy().

backup::copy(): A partial or sparse file-copying service.
Note: On Linux we never invoke sendfile(2) for
copying between files, nor copy_file_range(2) for copying
large files, because can be much slower than the alternatives.

backup::copy_or_stream(): Copy or stream a file.

backup::append(): The streaming streaming equivalent of backup::copy()..

backup_stream_zeropad(): Zero-pad the last tar block if needed.

backup_stream_append_async(): A variant of backup::append()
where the source file region is guaranteed to be immutable after the
call returns. Zero-copy Linux sendfile(2) is inherently risky for copying
data files that may be modified in place, because it could introduce
a race condition between a page write that runs concurrently with a
child process that is reading the data from the pipe. This could be
improved with io_uring later.

pread_write(): Allocate a buffer of up to 4 MiB.
This is the "slow path" of copying or streaming files.
For InnoDB data files, BACKUP SERVER TO invokes this on output that
has been opened in unbuffered I/O (O_DIRECT) mode, to avoid polluting
the Linux file system cache (block cache). For the InnoDB log,
regular buffered I/O performed best in our tests.

maria_backup_start(): The entry point for ENGINE=Aria.
Currently, the directory traversal is single-threaded and a memory hog.
This is to be improved in MDEV-39987. The Aria log and data file
copying were implemented by Michael 'Monty' Widenius and
Alessandro Vetere.

maria_backup_log_tail(): Copy the necessary part of the last
ENGINE=Aria log, by making use of block-cloning when possible.
The copy will be trimmed to the logical end of the backup.
The logic was developed by Alessandro Vetere.

InnoDB_backup::init(): Wait for a possible previous BACKUP SERVER
operation to reach the very end of InnoDB_backup::context::cleanup()
so that the context can be safely reused.

InnoDB_backup::queue: Collection of tablespace IDs and payload sizes
at the start of the backup, and the log_sys.first_lsn of log files
that have to be included in the backup. If any data file is created or
extended while the backup is executing, we must have the corresponding
write-ahead-log entries that we are copying since the latest
checkpoint that was completed when the backup started. If any
tablespaces are deleted during the backup, we may or may not copy
them, and the application of a FILE_DELETE record will remove them.
Similarly, applying FILE_RENAME or FILE_CREATE records will rename or
create files during recovery as needed.

InnoDB_backup::context::tracked: Log file queue for the dedicated
log-copying task InnoDB_backup::log_track().

innodb_backup_log_tracking(): Determine if log-tracking backup is
active, which blocks concurrent changes of innodb_log_file_buffering
and innodb_log_file_write_through.

log_sys.backup: Whether BACKUP SERVER is in progress. The purpose of this
is to make BACKUP SERVER prevent the concurrent execution of
SET GLOBAL innodb_log_archive=OFF or SET GLOBAL innodb_log_file_size
when innodb_log_archive=OFF.

log_sys.archived_checkpoint: Keep track of the earliest available
checkpoint, corresponding to log_sys.archived_lsn. This reflects
SET GLOBAL innodb_log_recovery_start (which is settable now), for
incremental backup.

fil_system.have_all_spaces: Whether all tablespace metadata is guaranteed
to be known. To speed up startup, InnoDB does not normally open
all tablespace files.

fil_space_t::create_lsn: Change to Atomic_relaxed and use this to
indicate tablespace creation LSN, in addition to indicate undo
tablespace rebuild LSN.

fil_space_t::backup_end: The first page number that is not being backed up
(by default 0, to indicate that no backup is in progress).

fil_space_t::BACKUP_BATCH_SIZE: The number of preceding pages that will be
covered by fil_space_t::backup_end. This is the unit of "page range locking"
during InnoDB backup.

fil_node_t::backup_name: A snapshot of the file name, sampled in
InnoDB_backup::init(). For backing up any files that were created before
the backup start checkpoint LSN, these names will be written to the backup.
On restore, the files will be renamed or deleted as needed, by
applying FILE_RENAME or FILE_DELETE records.

fil_node_t::rename(), fil_node_t::set_backup_name(),
fil_node_t::get_backup_name(), fil_node_t::~fil_node_t(),
fil_node_t::clear_backup_name(): Deal with fil_node_t::backup_name.

buf_page_t::write_fix_try(), buf_page_t::write_unfix_try(): Try to set
or unset a fake "write fix" on a page, to prevent concurrent flush()
during a backup batch. The atomic operations may run concurrently with
set_reinit() and set_freed(). The fake "write fix" does not prevent
any concurrent read or write of the page data in the buffer pool; it
only blocks writes to the underlying data file.

buf_page_t::flush(): Atomically test and set write fix, and
skip the operation if the fake "write fix" was set.

buf_page_t::set_freed(), buf_page_t::set_reinit(): Employ a
compare-and-exchange loop to accommodate for the "write fix".

innodb_backup_batch_wait(): Look up any pages that we are about to
back up. For any dirty pages, invoke buf_page_t::write_fix_try() to
try to set a fake "write fix" lock-free. If the page is currently
write-fixed between buf_page_t::flush() and
buf_page_t::write_complete(), acquire and release a page U-latch to
wait for the conflicting write to complete.

InnoDB_backup::backup_batch_start(),
InnoDB_backup::backup_batch_stop(): Adjust fil_space_t::backup_end and
fake "write fix" of dirty pages to protect the copying of a range of
pages from the underlying file.

InnoDB_backup::commit(): Enqueue the remaining log to be copied.

InnoDB_backup::checkpoint_complete(): If backup is running and
commit() has not been called, add each completed innodb_archive_log=ON
file to the queue. Else, skip or delete, as appropriate.

InnoDB_backup::archive_create(), innodb_backup_archive_create():
Hook for log_t::archive_create().

log_t::backup_start(): If we were running with innodb_log_archive=ON,
ensure that the latest file is a valid recovery starting point.
That is, wait for the latest log checkpoint to be within the file.

buf_flush_list_space(): Check for concurrent backup before writing each
page. This is inefficient, but this function may be invoked from multiple
threads concurrently, and it cannot be changed easily, especially for
fil_crypt_thread().

fil_ibd_create(): Set fil_space_t::create_lsn after the file has been
created.

dict_load_tablespaces(): Determine the size of each file if
upgrade==true. Backup depends on that.

buf_dblwr_t::begin(), buf_dblwr_t::end(), buf_dblwr_t::size():
Accessors to allow BACKUP SERVER to skip the contents of the
doublewrite buffer in the system tablespace. It is only useful for
crash recovery in case a data page had been incompletely written by
the time the server was killed. If the server is killed during a
backup, the backup will be incomplete and unusable anyway.
Furthermore, the page range locking makes page writes and backup
mutually exclusive.

Tested by: Matthias Leich
Tested by: Saahil Alam
Co-authored by: Michael 'Monty' Widenius
Co-authored by: Alessandro Vetere
Reviewed by: Thirunarayanan Balathandayuthapani
Reviewed by: Marko Mäkelä (the ENGINE=Aria part)
Oleg Smirnov
MDEV-40439 Evaluate cheap subconditions before expensive ones in WHERE and ON conditions

The arguments of AND and OR are evaluated left to right with
short-circuiting, in the order written in the query. An expensive
subcondition written first is therefore evaluated even for rows that
a cheap one would reject (AND) or accept (OR).

Reorder the arguments of each AND/OR so that the expensive ones come
after the cheap ones, keeping the relative order within each group.
The walk goes down into nested AND/OR and into trigger conditions.
A subcondition is expensive if it contains a subquery, a stored
function call, or an item that marks itself as expensive through
is_expensive_processor() (a UDF, MATCH()).

The reordering is applied to:
- the conditions attached to tables (select_cond, select->cond,
  on_precond), at the end of make_join_select() and before the
  attached_conditions_summary trace, so the trace shows the final
  order;
- BNL/BNLH scan filters, in make_scan_filter(). They are built from
  the original WHERE/ON, and check_join_cache_usage() may build them
  after make_join_select();
- outer_ref_cond and pseudo_bits_cond, the conditions a subquery
  checks once per execution before running its join.
The steps that later build new conditions from these (index condition
pushdown, removal of duplicate BNL conditions) take subsets and keep
the order.

Not covered yet: HAVING, exec_const_cond, terms added after
make_join_select() (the index condition remainder rejected by an
engine, HAVING moved to a table's condition), and SLEEP()/GET_LOCK(),
which do not mark themselves as expensive through
is_expensive_processor().
bsrikanth-mariadb
MDEV-41293: FederatedX: connection state after a net_read_timeout

Problem:
When reading the reply of the remote server times out, the client
library compiled into the server returns without closing the connection
and without setting a client error. FederatedX took the raw
ER_NET_READ_INTERRUPTED for an error of the remote server and went on
using a connection that still had the reply of the failed statement to
arrive. As a result:
* the next statement on that connection failed in various ways, either
  with ER_QUERY_ON_FOREIGN_DATA_SOURCE ("Server has gone away") or with
  an error packet that mysqltest took for a malformed packet;
* inside a transaction, the following statements continued on a new
  remote session in autocommit mode, so they were committed on the
  remote server one by one even though the local transaction was later
  rolled back. The savepoints of the lost session were also still
  remembered by FederatedX.
The same happened when the connection was lost while reading the reply.

Fix:
* After a timeout FederatedX closes the connection, so that the next
  statement opens a new one. The client still gets the error of the
  timeout, ER_NET_READ_INTERRUPTED.
* Whenever the connection is gone after a failed statement, timeout or
  not, the state of the lost session (autocommit mode, savepoints) is
  forgotten and the local transaction is marked to be rolled back, so a
  new connection starts the transaction again. This includes the case
  where the connection is lost while SET AUTOCOMMIT=0 is sent for the
  first statement of the transaction. The closed connection is also
  freed, which was leaked before.
* Errors of the client library are reported as
  ER_QUERY_ON_FOREIGN_DATA_SOURCE by ha_federatedx::info() as well, the
  same way as by the pushed down multi-table UPDATE/DELETE.
* The error state of ha_federatedx, read by info(), is initialized.
* A non-debug build checks the socket for EOF before it sends a command
  (net_clear()). If the remote server has already closed the connection, the
  write fails without the network layer raising an error, and the client
  would get ER_GET_ERRMSG ("Got error 10000 ...") intermittently. When the
  connection is lost and no error has been raised, raise
  ER_QUERY_ON_FOREIGN_DATA_SOURCE with the error of the client library. The
  COMMIT and ROLLBACK of the transaction still return their error to the
  caller.

Test: federatedx_pushdown_upd_del covers the timeout of a pushed down
multi-table UPDATE and DELETE and of reading a result, the statements
after it, the same inside a transaction, a connection killed in the
middle of a statement, a connection lost while SET AUTOCOMMIT=0 is sent
(also followed by a disconnect and a transaction in another session),
client library errors in info(), and that an error of the remote server
which merely has the code of ER_NET_READ_INTERRUPTED is not taken for a
timeout.
The two statements after the idle connection was killed accept
ER_NET_READ_ERROR (reported by the network layer in a debug build) or
ER_QUERY_ON_FOREIGN_DATA_SOURCE (non-debug build).
Marko Mäkelä
MDEV-41395 fixup: GCC 16 -Wstringop-overflow

fil_system_t::default_encrypt_next(): Dereference the iterator only once
to avoid a confusing and bogus -Wstringop-overflow by GCC 16.2.0.
Sergei Golubchik
MDEV-39596 Unexpected "No database selected" error for a CALL inside an anonymous block

For name1.name2() the PATH loop searches CURRENT_SCHEMA for a
package name1. In an anonymous block without a current database
resolve_current_schema() called copy_db_normalized(), which raised
ER_NO_DB_ERROR. But without a current schema there is no package
to find, name1.name2 can only be schema.routine.

Skip the unresolvable CURRENT_SCHEMA in an anonymous block, as it is
done outside of stored programs. Unqualified names never get here
without a current database, make_sp_name() and
Create_qfunc::create_func() reject them first.

Assisted-by: Claude:claude-opus-5-5
Oleg Smirnov
MDEV-40439 Evaluate cheap subconditions before expensive ones in WHERE and ON conditions

The arguments of AND and OR are evaluated left to right with
short-circuiting, in the order written in the query. An expensive
subcondition written first is therefore evaluated even for rows that
a cheap one would reject (AND) or accept (OR).

Reorder the arguments of each AND/OR so that the expensive ones come
after the cheap ones, keeping the relative order within each group.
The walk goes down into nested AND/OR and into trigger conditions.
A subcondition is expensive if it contains a subquery, a stored
function call, or an item that marks itself as expensive through
is_expensive_processor() (a UDF, MATCH()).

The reordering is applied to:
- the conditions attached to tables (select_cond, select->cond,
  on_precond), at the end of make_join_select() and before the
  attached_conditions_summary trace, so the trace shows the final
  order;
- BNL/BNLH scan filters, in make_scan_filter(). They are built from
  the original WHERE/ON, and check_join_cache_usage() may build them
  after make_join_select();
- outer_ref_cond and pseudo_bits_cond, the conditions a subquery
  checks once per execution before running its join.
The steps that later build new conditions from these (index condition
pushdown, removal of duplicate BNL conditions) take subsets and keep
the order.

Not covered yet: HAVING, exec_const_cond, terms added after
make_join_select() (the index condition remainder rejected by an
engine, HAVING moved to a table's condition), and SLEEP()/GET_LOCK(),
which do not mark themselves as expensive through
is_expensive_processor().