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
bsrikanth-mariadb
MDEV-39791: Handle count aggregate optimization for replay purpose

Separately dump into the optimizer context, the count aggregation value
of any one non-nullable column of a table if any are used in the query.
This is only suppoerted for myisam tables.

During opt_sum_query() call, record the count aggregation values into
the optimizer context recorder. Once the context is being stored into
the information_schema table, dump all these recorded values into it.

During replay, parse the count aggregate values and store them in memory.
Later when opt_sum_query() requires the count value, hook the recorded
value from the parsed context.
Thirunarayanan Balathandayuthapani
MDEV-34358  Encryption threads consume CPU and deadlock with DROP TABLE/purge

Problem:
========
1. Encryption threads busy-wait when no work is available:

When reaching fil_system.space_list.end(), fil_crypt_return_iops() is called
with wake=true, causing pthread_cond_broadcast() to wake all threads
unnecessarily, leading to CPU waste.

2. Tablespaces with CLOSING/STOPPING flags skipped during iteration:

Since DDL completion doesn't wake encryption threads, these spaces may never
be encrypted if threads sleep indefinitely.

3. For default_encrypt_list iteration, when spaces exist but none are
acquirable, threads need to wake others for cooperative retry, but this
case was not distinguished from fil_system.space_list.end().

4. IOPS are allocated before searching for tablespaces, wasting resources
during iteration when no I/O occurs.

5. Encryption threads use fil_crypt_threads_cond for two different purposes:
waiting for encryption work and waiting for IOPS allocation. When
fil_crypt_return_iops() or fil_crypt_realloc_iops() broadcasts after
releasing IOPS, it wakes ALL threads including those correctly waiting
for work, causing spurious wakeups and CPU waste.

6. When innodb_encrypt_tables or innodb_encryption_rotate_key_age is changed
during encryption thread iteration, threads continue with stale configuration
values, potentially missing tablespaces that should be encrypted or rotated
under the new settings.

7. The InnoDB encryption thread could deadlock with DROP TABLE and purge
operations in a three-way deadlock scenario:

- DROP TABLE thread holds lock_sys.latch and waits for the tablespace
pending reference count to reach zero before dropping the space.

- Encryption thread holds a tablespace reference and waits to acquire
the tablespace allocation latch in exclusive mode to call
fseg_page_is_allocated() for checking if a page is allocated before
encrypting it.

- Purge coordinator thread holds the tablespace allocation latch
in exclusive mode and waits to acquire lock_sys.latch
in shared mode for record lock operations.

This creates a circular dependency and leads to deadlock.

Solution:
=========
1. Implement timed wait with exponential backoff:

When space == fil_system.space_list.end() (applies to both default_encrypt_list
and space_list iteration when no acquirable spaces are found):
- First timeout: 5 seconds
- Subsequent timeouts: (timed_wait_count + 1) * 5 seconds (10s, 20s, 40s, 60s)
- After 5 consecutive timeouts (~135 seconds total), continue with 60-second
timed waits to ensure threads periodically recheck for state changes
- Timeout counter resets to 0 when woken by signal or when work is found

2. Move IOPS allocation from before tablespace search to after finding a space
that needs rotation. If allocation fails, set recheck=true to skip waiting
and immediately try next space.

Encryption threads would hold space references while waiting in
fil_crypt_alloc_iops(), blocking DROP TABLE. To prevent this,
encyption should do release-wait-reacquire pattern.

fil_crypt_alloc_iops(): Added nowait parameter (default false). When
nowait=true, returns immediately if IOPS not available instead of waiting.

fil_crypt_thread(): Try non-blocking IOPS allocation first with nowait=true.
Only if IOPS not immediately available:
- Save space ID and release space reference
- Wait for IOPS with nowait=false
- Reacquire space using fil_space_get_by_id() and space->acquire()
- If space dropped or stopping, release IOPS and skip

This ensures encryption threads never hold space references while waiting
for IOPS, allowing DROP TABLE operations to proceed without deadlock.

3. Introduce separate condition variable fil_crypt_iops_cond specifically for
IOPS allocation synchronization to prevent spurious wakeups:

- fil_crypt_threads_cond: Used in wait_for_work() for waiting when no
tablespaces need encryption. Signaled when settings change, new
tablespaces are created, or thread count changes.

- fil_crypt_iops_cond: Used in fil_crypt_alloc_iops() for waiting
when IOPS limit is reached. Signaled when IOPS are returned via
fil_crypt_return_iops(), released via fil_crypt_realloc_iops(), or
when srv_n_fil_crypt_iops is increased.

4. Added atomic version counter fil_crypt_settings_version that is incremented
whenever innodb_encrypt_tables or innodb_encryption_rotate_key_age changes.
Encryption threads capture the version at iteration start and check for
changes during iteration. If config changed, threads immediately restart
iteration from the beginning to ensure complete coverage with new settings.

New fields:
- fil_crypt_settings_version: Atomic counter to track configuration changes
- rotate_thread_t::timed_wait_count (uint8_t): Counts consecutive timeouts
  for exponential backoff
- rotate_thread_t::wait_for_work(): Implements timed/indefinite wait strategy
- rotate_thread_t::settings_version: Compares with fil_crypt_settings_version
  to restart encryption from the beginning

5. Fix three-way deadlock with trylock mechanism:
fseg_page_is_allocated(): Added optional caller_mtr parameter. Encryption
thread passes an mtr, allocation bitmap page latch is now correctly held
in that mtr until the caller commits it.

fil_crypt_get_page_throttle(): Use fil_space_t::x_lock_try() to acquire
the tablespace allocation latch non-blockingly. If the trylock fails,
back off and retry the same page to avoid deadlock. The bitmap page
S-latch is added to the mtr and held throughout the page read and
encryption operations. Release the tablespace exclusive latch immediately
after the allocation check to minimize contention, while the bitmap page
S-latch remains held in the mtr.

fil_crypt_rotate_page(): After 10 retries on page latch acquisition,
release IOPS, sleep 10ms, then try to reacquire IOPS with nowait=true.
If IOPS not immediately available, exit gracefully by setting first=true.
This periodic IOPS release allows other encryption threads working on
tablespaces being dropped to make progress and release their space
references, helping to break the deadlock scenario.
Yuchen Pei
MDEV-15621 [refactor] Partitioning cleanup

change p_column_list_val::fixed to a bool
remove redundant end label in partition_info::fix_column_value_functions
Alexey Botchkov
MDEV-37262 XMLISVALID() schema validation function.

XMLVALID function added to the XMLTYPE plugin.
Mohammad Tafzeel Shams
MDEV-38305: Expose adaptive hash index statistics in ANALYZE FORMAT=JSON

Expose InnoDB's Adaptive Hash Index (AHI) statistics through ANALYZE
FORMAT=JSON output to provide query-level visibility into AHI usage
and effectiveness. This allows DBAs and developers to monitor how well
the adaptive hash index is serving their workloads on a per-query basis.

The r_ahi_stats object (nested inside r_engine_stats) now reports four
key metrics: ahi_searches (successful AHI lookups), ahi_searches_btree
(AHI misses requiring B-tree fallback), ahi_rows_added (rows inserted
into AHI), and ahi_pages_added (pages indexed by AHI).

- btr_ahi_inc_searches(): Increment counter when AHI lookup succeeds.
- btr_ahi_inc_searches_btree(): Increment counter when AHI lookup fails
  and falls back to B-tree search.
- btr_ahi_inc_rows_added(): Increment counter when rows are added to
  the adaptive hash index structure.
- btr_ahi_inc_pages_added(): Increment counter when new pages are
  indexed by AHI.
- btr_cur_t::search_leaf(): Call btr_ahi_inc_searches() on successful
  AHI hit and btr_ahi_inc_searches_btree() on AHI miss to track search
  outcomes at the point where AHI is utilized.
- trace_engine_stats(): Output r_ahi_stats object with all four AHI
  counters in JSON format when any AHI activity is detected during query
  execution.
- ha_handler_stats: Added ahi_searches, ahi_searches_btree, ahi_rows_added,
  and ahi_pages_added fields to track per-query AHI statistics.
- ahi_stats.test: Comprehensive verification of AHI statistics reporting
  across different scenarios: insufficient accesses (no AHI build),
  threshold triggering (AHI construction), heavy warmup (full AHI
  utilization), and disabled AHI (verify zero statistics).
- check_ahi_status.inc: Reusable include file for executing queries with
  configurable warmup repetitions and extracting AHI statistics from
  ANALYZE FORMAT=JSON output using JSON path expressions.
- Add mtr parameter to btr_search_move_or_delete_hash_entries(),
  btr_cur_t::search_info_update(), btr_search_update_hash_on_insert(),
  btr_search_update_hash_ref(), btr_search_info_update_hash(),
  and btr_search_build_page_hash_index()
rusher
[misc] Add matrix entry that builds against latest C/C 3.3 branch
drrtuy
Don not download DuckDB engine code if it exists.
Yuchen Pei
MDEV-15621 Auto add RANGE COLUMNS partitions by interval

Allow auto partitioning by interval in PARTITION BY RANGE COLUMNS

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

where

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

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

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

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

When performing DML on such a table, it will first add partitions
by the specified interval until the partition covers the current time.

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

The partitions are named pN.

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

Note that TIMESTAMP is not allowed as a type for PARTITION BY RANGE
COLUMNS otherwise.
Sergei Petrunia
Add --replay-server-trace option.

Using

  ./mtr --replay-server --replay-server-trace

will now create files:

1.  mysql-test/main/$test_name.opt_trace.original
2.  mysql-test/main/$test_name.opt_trace.replay

The files show queries and optimizer trace outputs for the queries where:
1. We did try replaying in the server.
2. EXPLAIN output on the primary server and replay were different.
Marko Mäkelä
Use copy_file() for copying data files on Windows
PranavKTiwari
Added code change.
Marko Mäkelä
Implement page range locking
Alexander Barkov
MDEV-39022 Add `LOCAL spvar` syntax for prepared statements and SYS_REFCURSORs

This patch adds the following syntax:

OPEN c0 FOR LOCAL spvar_with_ps_name;
PREPARE LOCAL spvar_with_ps_name FROM 'dynamic sql';
EXECUTE LOCAL spvar_with_ps_name;
DEALLOCATE PREPARE LOCAL spvar_with_ps_name;

OPEN c0 FOR PREPARE stmt;
Lawrin Novitsky
Moved underlying libmariadb to 3.4 series - v3.4.9 atm

We have to support parsec auth plugin as minimum. zero config TLS is
also a big plus, but we don't change related options defaults - the
certificate is not yet validated by default.
ParadoxV5
Test for MDEV-39788

MDEV-39788 found that the recent refactor on the `main` (now 12.3)
branch missed the (inconsistent) detail that, unlike `@@relay_log_info`,
`@@master_info`’s line count _includes_ the line-count line itself.

This commit extends and simplifies the test
`rpl.rpl_read_new_relay_log_info` to `main.rpl_read_new_info` so it
* Checks this detail to remind future changes of this type of mistake.
* Covers `@@master_info` as well.

While here, this commit also includes a new-format version
of MDEV-38020’s test to double as the value read check.
Yuchen Pei
MDEV-15621 Auto add RANGE COLUMNS partitions by interval

Allow auto partitioning by interval in PARTITION BY RANGE COLUMNS

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

where

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

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

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

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

When performing DML on such a table, it will first add partitions
by the specified interval until the partition covers the current time.

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

The partitions are named pN.

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

Note that TIMESTAMP is not allowed as a type for PARTITION BY RANGE
COLUMNS otherwise.
Michael Widenius
Added documentation for how internal temporary tables works
Marko Mäkelä
fixup! da86ec2fc00f9c24677f7018069f1f62e6fc9f7d
Otto Kekäläinen
Fix Lintian flagged spelling in MariaDB Server repository

Fixes following Lintian nags:

    X: mariadb-plugin-connect: spelling-error-in-binary Exeption Exception [usr/lib/mysql/plugin/ha_connect.so]
    X: mariadb-client-core: spelling-error-in-binary specifiying specifying [usr/bin/mariadb-migrate-config-file]
bsrikanth-mariadb
MDEV-39538: Different costs when same range is read twice

When same range is used as a filter, once in the outer query block, and
the second inside a sub query such as: -

select * from t1
  where year(a) = 2010 and c < (select count(*) from t1 where year(a) = 2010);

The Optimizer Context had two records for multi_range_read_info_const() call,
but had different cost vector members.
The cause is that the first table considered index-only scan on the range,
while the second considered non-index only scan.

However, when replaying the context, the same range got matched
twice, and the costs corresponding to that got returned twice.
Hence the costs in the explain plan output differed as well.

Solution
========
Include a new field called "call_number", while recording range contexts
into the overall context. This way, we could even match the call_number
and return the appropriate cost during replay.
Jan Lindström
MDEV-39804 : Galera test failure on GCF-360

Allow error ER_QUERY_INTERRUPTED because TOI for DROP starts before
table is actually even opened, thus every node could
start it concurrently.
Alexander Barkov
MDEV-39022 Add `LOCAL spvar` syntax for prepared statements and SYS_REFCURSORs

This patch adds the following syntax:

OPEN c0 FOR LOCAL spvar_with_ps_name;
PREPARE LOCAL spvar_with_ps_name FROM 'dynamic sql';
EXECUTE LOCAL spvar_with_ps_name;
DEALLOCATE PREPARE LOCAL spvar_with_ps_name;

OPEN c0 FOR PREPARE stmt;
Lawrin Novitsky
Fix of UBSAN errors.

In one place copying from nullptr was possible. It could happen if the
streamed text resultset had to be cached(if the connection is requested
for other operation), and NULL value was present.
In couple of places int64_t could be overflown - had to be changed to
uint64 as it is defined plus othe implied changes.
In particular fetching MIN_INT64 value in text protocol could cause
that. Looks like all compilers work fine in this case as there is the
test for this.
Mohammad Tafzeel Shams
MDEV-38305: Expose adaptive hash index statistics in ANALYZE FORMAT=JSON

Expose InnoDB's Adaptive Hash Index (AHI) statistics through ANALYZE
FORMAT=JSON output to provide query-level visibility into AHI usage
and effectiveness. This allows DBAs and developers to monitor how well
the adaptive hash index is serving their workloads on a per-query basis.

The r_ahi_stats object (nested inside r_engine_stats) now reports four
key metrics: ahi_searches (successful AHI lookups), ahi_searches_btree
(AHI misses requiring B-tree fallback), ahi_rows_added (rows inserted
into AHI), and ahi_pages_added (pages indexed by AHI).

- btr_ahi_inc_searches(): Increment counter when AHI lookup succeeds.
- btr_ahi_inc_searches_btree(): Increment counter when AHI lookup fails
  and falls back to B-tree search.
- btr_ahi_inc_rows_added(): Increment counter when rows are added to
  the adaptive hash index structure.
- btr_ahi_inc_pages_added(): Increment counter when new pages are
  indexed by AHI.
- btr_cur_t::search_leaf(): Call btr_ahi_inc_searches() on successful
  AHI hit and btr_ahi_inc_searches_btree() on AHI miss to track search
  outcomes at the point where AHI is utilized.
- trace_engine_stats(): Output r_ahi_stats object with all four AHI
  counters in JSON format when any AHI activity is detected during query
  execution.
- ha_handler_stats: Added ahi_searches, ahi_searches_btree, ahi_rows_added,
  and ahi_pages_added fields to track per-query AHI statistics.
- ahi_stats.test: Comprehensive verification of AHI statistics reporting
  across different scenarios: insufficient accesses (no AHI build),
  threshold triggering (AHI construction), heavy warmup (full AHI
  utilization), and disabled AHI (verify zero statistics).
- check_ahi_status.inc: Reusable include file for executing queries with
  configurable warmup repetitions and extracting AHI statistics from
  ANALYZE FORMAT=JSON output using JSON path expressions.
- Add mtr parameter to btr_search_move_or_delete_hash_entries(),
  btr_cur_t::search_info_update(), btr_search_update_hash_on_insert(),
  btr_search_update_hash_ref(), btr_search_info_update_hash(),
  and btr_search_build_page_hash_index()
Marko Mäkelä
fixup! 5633fc2bf7fd070fd482f60063fd41c3298d36ac

Get a stack trace from Windows
Jan Lindström
MDEV-29909 : SST fails when table is defined with DATA DIRECTORY='/path/to' and datafile is larger than datadir space

Problem is that default path for SST is datadir and if there is not
enough space for all datafiles it fails.

In this patch wsrep_sst_tmp_dir configuration parameter is introduced.
User can now configure path where SST transfers datafiles in joiner
while operation is running. Additionally, if available transfer
size is compared to available disk space and if not necessary
big error is produced. When transfer is completed MariaBackup
will move datafiles to their correct locations.

Current rules for wsrep_sst_tmp_dir
* can be nullptr or empty and will not be used
* must be existing path and directory or not used
* may not be same as datadir or not used
* if total estimated SST payload available it should have enough available space
* similar to current behaviour temporal .sst directory is created
on path and this directory is removed after SST has finished
drrtuy
Replace configure-time git submodule with configure-time cmake FetchContent.
PranavKTiwari
Added test case.
Alexander Barkov
MDEV-39022 Add `LOCAL spvar` syntax for prepared statements and SYS_REFCURSORs

This patch adds the following syntax:

OPEN c0 FOR LOCAL spvar_with_ps_name;
PREPARE LOCAL spvar_with_ps_name FROM 'dynamic sql';
EXECUTE LOCAL spvar_with_ps_name;
DEALLOCATE PREPARE LOCAL spvar_with_ps_name;

OPEN c0 FOR PREPARE stmt;
rusher
[misc] add maxscale testing to CI
drrtuy
Initial version of DuckDB engine for MariaDB based on DuckDB 1.5.2.
Yuchen Pei
MDEV-15621 Auto add RANGE COLUMNS partitions by interval

Allow auto partitioning by interval in PARTITION BY RANGE COLUMNS

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

where

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

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

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

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

When performing DML on such a table, it will first add partitions
by the specified interval until the partition covers the current time.

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

The partitions are named pN.

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

Note that TIMESTAMP is not allowed as a type for PARTITION BY RANGE
COLUMNS otherwise.
Marko Mäkelä
fixup! bcdcf854ea15aaf41d4c7de7d6e00f6389451c6f
Marko Mäkelä
fixup! 9a7b3cd13bca6ae5b38408d9dfdb3908e6f2108d
Yuchen Pei
MDEV-15621 Auto add RANGE COLUMNS partitions by interval

Allow auto partitioning by interval in PARTITION BY RANGE COLUMNS

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

where

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

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

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

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

When performing DML on such a table, it will first add partitions
by the specified interval until the partition covers the current time.

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

The partitions are named pN.

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

Note that TIMESTAMP is not allowed as a type for PARTITION BY RANGE
COLUMNS otherwise.
Rex Johnston
MDEV-39495 Parallel Query: Use temporary work tables to ship results: basic test

Built on top of MDEV-39492, the parallel worker manager creates N
temporary tables and gives them to each parallel worker to populate.
At the conclusion of all worker threads, each row from the above tables
is added to the join_tab->table representing the query result.  To use

MariaDB [test]> set session parallel_worker_threads=10;
Query OK, 0 rows affected (0.001 sec)

MariaDB [test]> select SQL_BUFFER_RESULT * from t1 join seq_1_to_5 on t1.a = seq;
+------+------+-----+
| a    | b    | seq |
+------+------+-----+
|    1 | one  |  1 |
|    2 | two  |  2 |
|    1 | un  |  1 |
|    1 | one  |  1 |
|    2 | two  |  2 |
|    1 | un  |  1 |
|    1 | one  |  1 |
|    2 | two  |  2 |
|    1 | un  |  1 |
|    1 | one  |  1 |
|    2 | two  |  2 |
|    1 | un  |  1 |
|    1 | one  |  1 |
|    2 | two  |  2 |
|    1 | un  |  1 |
|    1 | one  |  1 |
|    2 | two  |  2 |
|    1 | un  |  1 |
|    1 | one  |  1 |
|    2 | two  |  2 |
|    1 | un  |  1 |
|    1 | one  |  1 |
|    2 | two  |  2 |
|    1 | un  |  1 |
|    1 | one  |  1 |
|    2 | two  |  2 |
|    1 | un  |  1 |
|    1 | one  |  1 |
|    2 | two  |  2 |
|    1 | un  |  1 |
+------+------+-----+
30 rows in set (0.010 sec)

Each worker has read the first table with a JT_ALL access method and
replicated the results back to the manager thread, which executes the
rest of the join.  In the above example there are 10 worker threads, so
10 copies of the join result.
drrtuy
Expose static symbols leveraged in DuckDB to stringify WHERE conditions for other MariaDB engines.
Sergei Petrunia
Fixups for:
Add @@time_zone to list of relevant server variables
Add in_predicate_conversion_threshold to opt_related_sys_vars[]:
Marko Mäkelä
fixup! 5633fc2bf7fd070fd482f60063fd41c3298d36ac
Thirunarayanan Balathandayuthapani
MDEV-39061  mariadb-backup compatible wrappers for BACKUP SERVER

scripts/mariabackup/mariabackup.sh: a drop-in wrapper that lets
existing mariabackup invocations drive the server-side BACKUP
SERVER command without changing user scripts.

mariabackup.sh covers all four mariabackup modes. --backup
translates into "BACKUP SERVER TO '<dir>'" via the mariadb client,
forwarding connection options, and layers --stream/--compress as
tar/gzip pipelines on the result. --prepare runs mariadbd
--bootstrap on backup.cnf so InnoDB applies the archived redo log.
--copy-back / --move-back drop a prepared backup into the datadir
via cp -r / mv.

--prepare --incremental-dir copies the incremental's ib_logfile*
into the base and advances innodb_log_recovery_target;
innodb_log_recovery_start stays pinned to the base checkpoint.

--apply-log-only maps to --innodb-force-recovery=3 to skip
rollback between incrementals.

--rollback-xa runs two passes: normal recovery, then a second
bootstrap with --tc-heuristic-recover=ROLLBACK.

--copy-back / --move-back refuse a non-empty datadir unless
--force-non-empty-directories is set, and print the post-action
chown / systemctl start commands.

For incremental --backup, innodb_log_archive_start is treated as a
startup-only, read-only server invariant: the wrapper reads
@@global.innodb_log_archive_start and fails fast if the archive
floor exceeds the base backup's end LSN.

Limitations:
--export is accepted but not yet implemented; the wrapper prints
a warning and runs plain recovery without producing the per-table
.cfg files needed for ALTER TABLE ... IMPORT TABLESPACE.

mbstream.sh shims the mbstream CLI onto tar, dropping
mbstream-only flags (-p/--parallel) so legacy pipelines keep
working.

README.md maps every supported option per mode to its BACKUP
SERVER equivalent and documents the backup.cnf format.

Add include/have_mariabackup_wrapper.inc redirects $XTRABACKUP to
the wrapper so a test opts in by sourcing one file; skips when the
wrapper, bash, or the mariadb client is unavailable.

wrapper_basic.test: exercises full backup, streaming, compression,
the ignored legacy options.