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
Anway Durge
MDEV-40819: Do not crash on DuckDB TIME values beyond 24 hours

MariaDB TIME is a duration up to 838:59:59, but SELECT converted DuckDB
TIME through ToString(), which asserts clock-time validity in debug
builds. Convert microseconds to MYSQL_TIME directly instead.

SQL DML quoted TIME strings, so '25:00:00' failed conversion. Store
microseconds through mariadb_time() instead, matching the appender path.
Marko Mäkelä
Stream empty InnoDB system tablespace files
Aleksey Midenkov
MDEV-17613 Test cleanup: sorted result for versioning.partition

Avoid result fluctuations with different storage engines.
Aleksey Midenkov
MDEV-17613 Refactor prune_partitions() to build into a dedicated bitmap

prune_partitions() built the used-partition set directly in
read_partitions: it cleared it, let find_used_partitions*() fill it, and
in the index-merge case used it as scratch too - both buffer and result.

The patch adds PART_PRUNE_PARAM::parts_bitmap and builds the return
into it instead. read_partitions is produced from it only at the end:
copied when the condition was analyzed (IMPOSSIBLE included,
parts_bitmap left empty), or set to all locked partitions via
mark_all_partitions_as_used() when nothing could be pruned.

Pure refactoring: read_partitions ends up identical on every path. It
decouples the pruning scratch from read_partitions, so a later commit can
switch the final bitmap_copy() to bitmap_intersect() - letting pruning
narrow a pre-set read_partitions (e.g. a versioned table selecting only
its current partition) instead of overwriting it.
Monty
Remove C++ STL machinery from sql/rpl_*info_file.* and CHANGE MASTER parser

Replaces std::function / std::optional / std::unordered_map /
std::unordered_set / std::string_view and template-heavy code in the
master.info / relay-log.info persistence layer with plain MariaDB
idioms.
- On-disk file format unchanged.
- SQL syntrax unchanged
- No notable test changes, except those for which new error messages was
  added and output from SHOW VARIABLES.
- master_* CLI options moved from inline globals in
  rpl_master_info_file.h to Sys_var_* in sys_vars.cc (READ_ONLY,
  AUTO_SET on use_gtid and heartbeat_period). master_heartbeat_period
  is Sys_var_double (seconds); master_use_gtid is Sys_var_enum. Global
  storage sits outside HAVE_REPLICATION so libmariadbd links.
  - This adds the rpl_master_info variables to SHOW VARIABLES
- Value classes are no longer templates. Persistent renamed to Value;
  Optional_* classes renamed (Uint_value_with_default, Path_value,
  etc.).  has_value flag in the base; is_default()
  non-virtual. Multi-line method bodies moved to new rpl_info_file.cc
  and rpl_master_info_file.cc.
- VALUE_LIST (Mem_fn array) and VALUE_MAP (std::unordered_map)
  replaced by per-instance Value *const value_list[] / value_map[]
  arrays + a static TYPELIB populated at first construction from each
  Value's variable_name. seen-set dedup is now a uint32 bitmask.
- Default handling is not anymore done with using special values.
  Setting a value to default is now done by calling set_default(),
  which sets the has_value member to 0 and stores the default value
  in the variable.
  When reading a value, one does not have to check if the value is
  a default value.
  This makes the interface cleaner and easier to manage.
- CHANGE MASTER TO parser no longer builds std::function lambdas:
  LEX_MASTER_INFO carries mi_used_options / mi_used_default bitmasks
  plus plain value fields; change_master() applies them via bit-test +
  direct copy or set_default(). std::optional<T> and trilean are gone
  from the value class API.
- Heartbeat_period_value::load_from uses my_strtod() instead of
  my_decimal for the on-disk format. from_decimal kept for CHANGE
  MASTER.
- RocksDB #if __cplusplus < 201703L guard at the top of
  rpl_master_info_file.h dropped; header now compiles in C++11.
  cstdint dropped; uint32_t / UINT32_MAX in touched code replaced by
  MariaDB's uint32 / UINT_MAX32. include/my_global.h gains
  INT_BUFFER_SIZE next to LONGLONG_BUFFER_SIZE.
- Pointer-style throughout (T * not T &). Multi-line inline functions
  used in more than one place moved to .cc files per style.

New features:
- SHOW VARIABLES now shows the master default values.
- If there is an wrong value for a row in a master.info file the
  corresponding variable name and the value is now written to the
  error log.

Bug fixes uncovered by the refactor:
- master_ssl_verify_server_cert had a pre-existing inconsistency with
  security implications: the documented default and the Sys_var_mybool
  default are TRUE (validate the master's certificate), but the OLD
  init_ssl_config() set the per-Master_info field to FALSE on RESET
  SLAVE ALL. Concretely: after RESET SLAVE ALL followed by a bare
  CHANGE MASTER TO (no explicit MASTER_SSL_VERIFY_SERVER_CERT), the
  slave would connect with MASTER_SSL=1 enabled but with certificate
  validation silently disabled.
  Fixed by not resetting master_ssl_verify_server_cert in
  RESET SLAVE ALL;
- As some replication setups sets master_ssl_verify_server_cert= 0
  while other does not do that, the expected value used in
  check-testcase.inc "Master_SSL_Verify_Server_Cert No" does not make
  sense.  The value of this variable is now ignored by
  check-testcase.inc
- The old code would fail to read a master info file if there was a new
  variable longer than 23 characters. This is now extended to 80.
- Removed warning on RESET SLAVE that "RESET SLAVE is implicitly changing"
  the value of 'Using_Gtid' from 'No' to 'Slave_Pos'" as this change did
  not really happen. This error was given if the master could support
  GTID but the user had specified directly or indirectly to not use GTID.

Other things
- Size of mariadb (optimize build) shrunk with 1,408,009 bytes
  (Based on 'dec' from the size command)
- Size of code in sql directory did not change notable.
- Reworked Master_use_gtid_value to remember users last choice and use
  this as default when connecting to slave.
  Replace mi->using_gtid, with was a pointer to master_use_gtid, with
  two functions:
  - using_gtid() returns the current mode, for the code that needs the
    value itself (SHOW SLAVE STATUS and performance schema output,
    comparisons against CURRENT_POS and SLAVE_POS).
  - is_gtid_supported() tells whether the connection uses gtid at all.
    The many callers that compared the mode against USE_GTID_NO with
    == or != now call this function instead of testing the enum value.
- Master_use_gtid_value::supported() did the same test as
  is_gtid_supported() and is removed
- Removed some operators in rpl_master_info_file.* that hid
  that functions was used to read and update values.

Behavior changes:
- The last used value in CHANGE MASTER for MASTER_USE_GTID will be
  remembered for future CHANGE MASTER for the same connection. This is
  true even if RESET SLAVE is called.
- If Using git is not used with change master but log_file_name, pos,
  relay_log_name or relay_log_pos is specified this is treated as
  if Using_git=NO would have been given. (Old behavior, but not
  documented).
--auto-master-use-gtid does nothing. The default value of
  master_use_gtid is by default SlavePos that will automatically
  change to NO if the master does not support GTID. This already
  handles what auto-master-use-gtid tries to do.
- CHANGE MASTER's MASTER_HEARTBEAT_PERIOD now works like other MASTER
  options:  When set to a value, this value is kept until value is
  changed or set to DEFAULT. The DEFAULT is
  @global.master_heartbeat_period if this is set as startup, if not
  then @global.slave_net_timeout/2 is used.
Aleksey Midenkov
MDEV-17613 Read sole now-partition for non-historical versioned query

When a versioned table stores its history in partitions, an implicit
query (no FOR SYSTEM_TIME) needs only current rows, which all live in
the current partition. vers_setup_conds() no longer adds a row_end
condition for such a table, and prune_partitions() restricts
read_partitions to the current partition instead.

read_partitions is reset to all partitions every statement in
open_table(), so the restriction must be applied on each execution.
prune_partitions() runs on each execution, also for merged derived
tables and views, where vers_setup_conds() is not called again. Both
functions guard with !vers_conditions.was_set(). In prune_partitions()
!is_set() would not do: set_all() sets type (flips is_set()) but not
orig_type (was_set()), so is_set() would be true on prepared-statement /
stored-procedure re-executions and skip the restriction, leaving history
partitions readable - rows matched then grow on each execute/call.

A query whose time comes from @@system_versioning_asof must still read
history, so the restriction is also skipped when from_sysvar is set.
init_from_sysvar() sets this new flag rather than orig_type, so
was_set(), print() and the write lock check still see only an explicit
FOR SYSTEM_TIME. Like orig_type, the flag persists to prepared-statement
re-execution.

EXPLAIN no longer shows "Using where" for such a query: correctness now
comes from partition selection rather than a row_end filter.

With sql_safe_updates, DELETE without WHERE on such a table is now
rejected even with LIMIT, as for a non-versioned table. With an index
on row_end, UPDATE or DELETE without WHERE (UPDATE also without LIMIT)
is now rejected too: it was accepted as a range scan on row_end. A
versioned table without SYSTEM_TIME partitions keeps the old behavior
(MDEV-41393).
Aleksey Midenkov
MDEV-17613 Refactor prune_partitions() to build into a dedicated bitmap

prune_partitions() built the used-partition set directly in
read_partitions: it cleared it, let find_used_partitions*() fill it, and
in the index-merge case used it as scratch too - both buffer and result.

The patch adds PART_PRUNE_PARAM::parts_bitmap and builds the return
into it instead. read_partitions is produced from it only at the end:
copied when the condition was analyzed (IMPOSSIBLE included,
parts_bitmap left empty), or set to all locked partitions via
mark_all_partitions_as_used() when nothing could be pruned.

Pure refactoring: read_partitions ends up identical on every path. It
decouples the pruning scratch from read_partitions, so a later commit can
switch the final bitmap_copy() to bitmap_intersect() - letting pruning
narrow a pre-set read_partitions (e.g. a versioned table selecting only
its current partition) instead of overwriting it.
Aleksey Midenkov
MDEV-17613 Refactor prune_partitions() to narrow read_partitions instead of full rewrite

read_partitions is reset to all partitions (or the PARTITION() set) once
per statement in open_table() (set_partition_bitmaps()), and
prune_partitions() only intersects it with lock_partitions and the pruned
set instead of rebuilding it.
mark_all_partitions_as_used() is dropped.

Pure refactoring, results unchanged. It lets a restriction set on
read_partitions before pruning survive it - needed when read_partitions
is pre-restricted, e.g. a versioned table whose history is partitioned
reads only the current partition.
Monty
Have mtr also use /usr/lib64/libgalera_smm.so when searching for wsrep lib
Marko Mäkelä
MDEV-41565 Better innodb_log_recovery_target check

log_parse_start(): Account for the mini-transaction trailer.

Also, fix the spelling: fulfil, not "fulfill".
Alessandro Vetere
MDEV-32286 Reuse remembered clustered leaves in secondary-index scans

Row_sel_get_clust_rec_for_mysql::operator() descends the clustered B-tree
from the root for every row whose clustered-index record a secondary-index
scan must read, although consecutive rows land on the same clustered leaf
page wherever the secondary order tracks the clustered one. A non-covering
scan needs one for every row, a locking read needs one whatever the
secondary index holds, because an exclusive select lock type makes
ha_innobase::build_template() build its template against the clustered
index, and a covering scan needs one for every row of a secondary leaf
whose PAGE_MAX_TRX_ID its read view cannot see. ANALYZE FORMAT=JSON
charges each descent its full height, and those descents are nearly the
whole cost: the secondary index is charged its own descent and one page
for each further leaf, and nothing per row, because the position that its
cursor holds between two rows is restored optimistically, which latches
the leaf again without counting an access. So pages_accessed is the row
count times the height of the clustered index, plus a handful: 1000 rows
over a 2-level clustered index cost 2006 and 750 rows over a 3-level one
cost 2291, where a full table scan of the same data costs 23 and 110.

Let a handle remember the clustered leaves that the lookups of one
statement reached, and let the next lookup try them before it descends
again. The reasoning behind each value and each rejection is in the
comments beside it.

row0mysql.h defines clust_leaf_hint_slot, which names one leaf: its page
number, copies of its first and last user record truncated to the key
fields, which bound the key range that the leaf held when it was
remembered, and the rec_get_offsets() of both. A slot of a
leaf that had no right sibling names no last key, because every key above
the last record of the rightmost leaf still belongs to it.
CLUST_LEAF_HINT_SLOTS (4) slots hang off the new
row_prebuilt_t::clust_leaf_hint, beside clust_leaf_hint_mru, the most
recently used order held as slot numbers, and clust_leaf_hint_n and
clust_leaf_hint_miss, the used-slot count and the miss counter.

row0sel.cc holds the policy. row_sel_clust_leaf_hint_covers() compares a
key against the remembered ranges, so a lookup that no slot can answer
costs no buffer pool access and no pages_accessed.
row_sel_clust_leaf_hint_search() probes the first slot that covers the key
and moves it to the front of the order.
row_sel_clust_leaf_hint_remember() records the leaf that a descent landed
on, and refreshes the slot of a leaf that is remembered already rather
than spend a second one on the same page. Two descents fill no slot: the
lookups of a statement before lookup CLUST_LEAF_HINT_MIN_LOOKUPS (4), and
a leaf that is the root. row_sel_clust_leaf_hint_armed() stands a scan
down once the slots stop paying for themselves: a miss adds
CLUST_LEAF_HINT_MISS_WEIGHT (2) to the miss counter and a hit takes one
away, so a scan gives the slots up where it answers too little of its
lookups to pay for them, CLUST_LEAF_HINT_MAX_MISSES (8) misses with no hit
between them still reach the threshold, and one lookup in
CLUST_LEAF_HINT_RETRY (1024) starts the count again, so a scan whose order
becomes correlated only later recovers. Both halves of the cost stop
there, the test of the slots and the copies that refresh them.
Row_sel_get_clust_rec_for_mysql::operator() calls all of this in place of
its btr_pcur_open_with_no_init(), and only where the adaptive hash index
is disabled. A guess of that index lands on the record with no page-local
search and no page access to charge, but neither of the two is faster for
every shape of index and scan, and a hint that runs first stops that index
from adding entries. The adaptive hash index is off by default, so the
hints are active in a default configuration.

btr0cur.h and btr0cur.cc add btr_cur_t::try_leaf_hint(), a PAGE_CUR_LE,
BTR_SEARCH_LEAF search on one named leaf, which starts from the record
where the last search of that leaf landed while that record is still valid,
and from a binary search of the page otherwise. It acquires the page with
buf_page_try_get(): a hint is never derived from a latched parent page,
so by the time it is tried it can precede the caller's already-latched
secondary-index leaf in the latching order, where a blocking wait can
deadlock. It then rejects the page unless the checks that it makes on the
latched frame put the match on it. Those checks are the sole authority on
the result, so a stale range costs a wasted probe or a needless descent,
never a wrong result, and the ranges need no invalidation protocol.

A slot also holds a btr_leaf_step, where the last search of its leaf
landed: the block, its buf_block_t::modify_clock and the record. A
record pointer stays valid while the block holds the same page and the
clock has not moved, because eviction, deletion and reorganization
advance the clock and an insertion moves no record. While the step is
valid, btr_cur_t::try_leaf_hint() starts from that record instead of a
binary search of the page. page0cur.cc adds page_cur_search_near(),
which compares the key with the record and with one neighbour, and
positions the cursor on the record, on the record after it or on the
record before it. It refuses any other answer and leaves it to
page_cur_search_with_match(). So a scan whose consecutive rows are
neighbours in the clustered index finds each row with two comparisons. A
scan that reads the clustered leaf in falling key order does the same: a
secondary key that falls as the primary key rises, or a secondary index
read backward. Where the secondary order tracks the clustered one with
gaps between the rows, the step fails, and btr_leaf_step::expect (below)
leaves the leaf to the binary search. The step refuses the metadata
pseudo-record, which compares below every key on 0 fields, and a record
before it that is the infimum. A debug build checks every successful
step against the binary search.

A failed step costs comparisons whose outcome no branch predictor can
guess, so btr_leaf_step::expect gates it. The next search tries the step
only where this one landed where a step would have: on the remembered
record, or right before or after it. The successor link of the landing
record tells the record right before, because finding a predecessor takes
a walk of the page directory. So a scan in random order over a few leaves,
which the slots answer in full, keeps the binary search and pays only the
tests that set expect. row_sel_clust_leaf_hint_remember() sets the step on
the record where a descent landed, with expect set.

ha_innodb.cc: ha_innobase::reset() zeroes the used-slot count and the miss
counter per statement, matching autoinc_last_value. row0mysql.cc:
row_prebuilt_free() frees the key buffers that the slots own.

innodb.clust_leaf_hint measures pages_accessed over key orders that differ
in how closely the secondary order tracks the clustered one, and further
tables check query results over the record formats and key shapes
that a clustered-index lookup has to read, down to the metadata
pseudo-record of instant ALTER TABLE, to leaves that split and merge while
a locking read walks them, and to a record that a remembered leaf supplies
for a scan that must then rebuild an older version of it. Two of the
tables scan a covering index, which reads a clustered record under an
exclusive select lock type and under a PAGE_MAX_TRX_ID that the read view
cannot see. clust_leaf_hint_off_debug runs the same body with the hints
turned off, through a debug switch that returns before a lookup tests or
refreshes the slots, so a diff of the two .result files is what the hints
save: 2006 to 1031 (2-level clustered index), 2291 to 1011 (3-level), 4006
to 2015 (two interleaved key ranges), 12016 to 9078 (locality in the
second half alone) and 20020 to 10045 for a covering scan that FOR UPDATE
makes non-covering, where the same scan without FOR UPDATE costs 20 in
both files. Two orders with too little locality to pay for the slots give
them up early and end within a hundred accesses of the unhinted count:
20020 to 19966 (decorrelated) and 20020 to 19999 (shuffled).

Two further tables in innodb.clust_leaf_hint cover the step back. t16
scans a secondary index whose keys fall as the primary key rises, and
reads an ascending secondary index backward: 20020 to 10047 for both. t17
runs the inverse scan on ROW_FORMAT=REDUNDANT with an instantly added
column. innodb.clust_leaf_hint_backward_debug reaches the two refusals of
the step back that no count shows, at the infimum and at the metadata
pseudo-record. A lookup reaches either only for a key that left the
clustered index while the slot still covers it. The rollback of an insert
over a delete-marked record removes the key while purge leaves the
secondary entry, and a PAGE_MAX_TRX_ID that the read view cannot see makes
the reader look that entry up. A debug switch writes each refusal to the
error log, and the case reads both back.

A slot stays valid when dict_index_t::clear_instant_alter() changes the
index under a reader that holds a shared metadata lock, between two
lookups or during one. That clear runs where purge or rollback removes
the last user record of a single-page table whose only instant change was
ADD COLUMN, and it raises dict_index_t::n_core_fields.
rec_copy_prefix_to_buf() writes the copies in the format of an index
without instant ADD COLUMN, their offsets are made with them, and the
comparisons depend on the key fields alone, which no instant change moves.
So the slots do not record n_core_fields, and the hints read it only under
a latch on a clustered leaf.

innodb.clust_leaf_hint_instant_alter makes a lookup compare against a slot
that a clear has outlived. The clear needs the clustered index to lose the
last user record of its root page, while no leaf that is the root fills a
slot, so the tree has to shrink between the two, which purge does there.
The reader therefore reads uncommitted rows at READ UNCOMMITTED and waits
in a stored function while a rollback and purge take them away, and one
row that arrives above the position it stopped at is the lookup that
tests the slots.

main.rowid_filter_innodb: 90 to 88, and its ahi combination unchanged.

A static_assert in Row_sel_get_clust_rec_for_mysql::operator() fails to
compile if btr_search.enabled stops being a Boolean that applies to every
index, because the test !btr_search.enabled that stands the hints down
would then have to become !btr_search.is_enabled(clust_index).
Aleksey Midenkov
MDEV-17613 bitmap_range() for iteration over partition bitmap

Returns bit range for partition element.
Pekka Lampio
MDEV-39143 follow-up: fix review findings in MySQL JSON replication

Addresses issues found in review of 16ce07df15d (Replicate MySQL binary
JSON to a MariaDB slave), in both the new code and two pieces of
pre-existing core replication code it interacts with. Issue numbers
below refer to the review's own numbering (I001-I039).

Crashes and silent corruption:
- A truncated or malformed Json_diff vector in a
  PARTIAL_UPDATE_ROWS_EVENT could NULL-deref the slave:
  safe_net_field_length_ll() nulls its cursor on a truncated
  encoding, and nothing checked for that. (I001, I002)
- The same unchecked-cursor bug existed in the after-image header
  parser, which additionally had no bound on partial_bits
  consumption and did not reject out-of-range value_options. (I005)
- A diff that MySQL's own applier would REJECT (its path doesn't
  match the slave's document) was silently applied as a no-op
  instead of halting replication - the opposite of MySQL's own
  behavior (ER_COULD_NOT_APPLY_JSON_DIFF). (I004, I025)
- Corrupt MySQL binary JSON in a diff value was committed as a JSON
  null instead of being detected: Field_mysql_json::val_str() pushes
  an error but returns a non-null empty string on failure, so the
  previous `if (!vtext)` check could never fire. (I003)
- The same silent-corruption class existed on the full-document
  conversion path for a native-JSON target. (I038)
- find_row() failing to locate the matched row still read the base
  document from stale record[1] data, and did not record the
  correct stream position on error, risking a parse desync under
  slave_exec_mode=IDEMPOTENT. (I020, I033)

Error reporting:
- Most failure paths in apply_partial_json_column() returned a bare
  HA_ERR_GENERIC with nothing reported to the DBA, contradicting the
  function's own documented contract. (I024, I026, I027, I031)
- Fixed messages use my_printf_error() rather than
  rgi->rli->report(): do_exec_row()'s caller unconditionally
  re-derives Last_SQL_Error from thd->is_error() once a row event
  returns an error, overwriting anything rgi->rli->report() had
  already recorded with a generic "Got error NNN from storage
  engine" wrapper - only a message already in the diagnostics area
  survives.
- check_wrong_column_usage() only checked an UPDATE event's
  before-image bitmap, so an incompatible column visible only in
  the after-image (binlog_row_image=MINIMAL) was silently dropped
  from the write set instead of being reported. (I006)
- slave.cc's IO-thread statement-end tracking was missing
  PARTIAL_UPDATE_ROWS_EVENT, which could produce a false-positive
  "did not receive an expected Rows-log end-of-statement" error.
  (I008)
- Documented, but did not fix (needs generic plugin refcounting,
  out of scope here): Type_handler::handler_by_name() releases its
  plugin lock before the caller is done using the returned handler,
  a pre-existing race in the data-type plugin framework. (I039)

Design/consistency:
- A MySQL JSON source converting to a plain string/blob target was
  always classified as safely widening regardless of actual size;
  it is now classified by the target's real declared capacity like
  any other blob/string conversion, so a document that doesn't fit
  requires slave_type_conversions=ALL_LOSSY instead of silently
  truncating under ALL_NON_LOSSY alone. (I007)
- apply_partial_json_column() now builds its per-diff JSON function
  Items on a private MEM_ROOT freed per row/column, instead of
  thd->mem_root (the statement-level arena), avoiding unbounded
  growth on a large multi-row UPDATE. (I028)
- An open-coded bitmap test duplicating the existing
  rpl_bitmap_is_set() helper was replaced with a call to it. (I032)
- Corrected a comment overstating MySQL JSON -> native JSON
  conversion as lossless; a JSONB OPAQUE temporal/decimal value
  loses its original type tag even though its content survives.
  (I036)
- Deduplicated the "MYSQL_JSON" plugin-name literal into one
  constant. (I037)

Tests:
- Switched the existing tests from unconditional INSTALL/UNINSTALL
  SONAME to the repo's have_type_mysql_json.inc convention. (I013)
- Corrected a test comment that misattributed why the from-the-end
  array-index diff is refused. (I014)
- Extracted the tests' duplicated binlog-fixture-injection and
  post-replication resync logic into shared include files. (I015)
- Added tests for the missing-plugin, conversion-length-gating, and
  corrupt-diff-vector error paths above. (I016)

Assisted-by: Claude AI
Monty
Avoid strlen() calls in Explain_index_usage and String_list

Use Lex_ident_column* instead of char* as parameters
Aleksey Midenkov
MDEV-17613 bitmap_range() for iteration over partition bitmap

Returns bit range for partition element.
Mohammad Tafzeel Shams
MDEV-37467: InnoDB Instant ALTER TABLE is not crash safe

The hidden metadata record of instant ALTER TABLE was not written
crash-safely, and recovery could fail to roll it back. These are
independent problems.

First, the metadata record may include externally stored BLOB
metadata. The existing BLOB storage path in
btr_store_big_rec_extern_fields() writes the clustered index record
first, with zero BLOB pointers, and only fills in the BLOB pointers
afterwards. If the server is killed after the mini-transaction that
wrote the (incomplete) metadata record was durably committed, but
before the BLOB pointers were written, the table could become
inaccessible on recovery.

Make metadata BLOB storage crash-safe by writing the BLOB pages and
computing their pointers before the metadata record itself is
inserted or updated, so that the record is always written with
complete BLOB pointers. If the server is killed before the metadata
record is written, the already-written BLOB pages are merely
orphaned, which is safe.

Second, trx_undo_report_row_operation() writes the undo log record in
a mini-transaction of its own, which is committed before the
mini-transaction that writes the metadata record. Because
innobase_instant_try() had already updated SYS_COLUMNS and SYS_TABLES
in earlier mini-transactions, a kill in between left a durable undo
log record for the table while the metadata record was unchanged. On
recovery, trx_resurrect_table_locks() would then load the table
definition before the incomplete transaction was rolled back. The
data dictionary described the table as it would be after the
operation, while the metadata record still described it as it was
before, and btr_cur_instant_init() failed on that disagreement.

Write the undo log record of the metadata record in the same
mini-transaction that inserts or updates the record, so that the two
cannot be separated by a crash: until that mini-transaction is
committed, neither of them is durable.

An undo log record is never split between pages. If the DEFAULT
values of the columns being added are large enough that the undo log
record for updating the metadata record would not fit on one page,
innobase_instant_try() would fail. Determine this before the operation
starts, so that it can be performed by another algorithm instead.

Third, the table definition that recovery loads need not correspond to
the metadata record. dict_load_table_one() reads the committed version
of the SYS_TABLES record, and escalates to READ UNCOMMITTED only when
it finds a SYS_COLUMNS record that was written by a transaction that is
still active. The number of SYS_COLUMNS records that dict_load_columns()
reads is derived from SYS_TABLES.N_COLS, which was read from the
committed version. The record of a column that the operation appended is
located after that many records, so it is never read and the operation
goes unnoticed. Only an instant ALTER TABLE that merely appends columns
can escape this way: ADD COLUMN ... FIRST, DROP COLUMN and column
reordering rewrite the SYS_COLUMNS records of already existing columns.

Detect this on the SYS_TABLES record itself, which is located by table
name and therefore does not depend on N_COLS. Every instant ALTER TABLE
that changes the columns updates that record, because
innobase_instant_try() invokes innodb_update_cols().

Fourth, the rollback writes a metadata record that comprises fewer
fields than the table definition describes, because
btr_cur_trim_alter_metadata() shortens it to the number of fields that
it comprised before the operation. That number determines the size of
the null flag bitmap, and hence the position of the array of field
lengths. rec_init_offsets_comp_ordinary() derives it from the record,
while the two functions that write the record derived it from the table
definition and asserted that the two agree.

- btr_store_big_rec_metadata():
  New function to store the off-page columns of a metadata record
  ahead of time. Each BLOB page is allocated and linked in its own
  mini-transaction, and the resulting BLOB pointers are written
  directly into the (heap-resident) index entry. On failure, it frees
  any pages it already allocated and resets the pointers to zero.

- btr_free_big_rec_metadata():
  New helper to free the BLOB pages written by
  btr_store_big_rec_metadata() and reset the entry's BLOB pointers
  to zero, used both on failure inside that function and by its
  callers when the metadata record ends up not being written.

- row_ins_clust_index_entry_low():
  For a metadata entry that needs external storage, convert it to a
  big record and call btr_store_big_rec_metadata() (with
  log_free_check() allowed, since no latches are held yet) before
  inserting the record. On failure, free the metadata BLOBs and
  convert the entry back.

- btr_cur_pessimistic_update():
  When updating a metadata record that requires external storage,
  call btr_store_big_rec_metadata() (without log_free_check(),
  since index and page latches are held) before modifying the record,
  and free the temporary big_rec vector via btr_free_big_rec_metadata()
  or dtuple_big_rec_free() on the various failure/success paths.

- btr_cur_optimistic_insert():
  Remove the special-cased jump to convert_big_rec for metadata
  entries, since their BLOBs are now always stored ahead of time by
  the caller; assert that a metadata entry never needs external
  storage at this point.

- innobase_instant_try():
  Since btr_cur_pessimistic_update() now stores metadata BLOBs
  before updating the record, big_rec is always NULL here; assert
  this instead of calling btr_store_big_rec_extern_fields().

- trx_undo_report_row_operation():
  New parameter caller_mtr. If it is specified, the undo log record
  is written in that mini-transaction, which is never committed or
  restarted here. An undo log page is added within the same
  mini-transaction if the record does not fit on the current one. A
  temporary table never uses the caller's mini-transaction, because
  that would require changing its logging mode. All other callers
  pass NULL and are unaffected.
  Encapsulate the parameters that describe the row change
  (clust_entry, update, cmpl_info, rec, offsets) in the new type
  trx_undo_row_op, which of the fields are set depends on the
  operation, which the type documents.

- btr_cur_ins_lock_and_undo(), btr_cur_upd_lock_and_undo():
  For an instant ALTER TABLE metadata record, pass the
  mini-transaction that is going to insert or modify the record.

- trx_undo_max_rec_size():
  New function to determine the maximum size of an undo log record,
  that is, the space available on an empty undo log page.

- ha_innobase::check_if_supported_inplace_alter():
  Refuse ALGORITHM=INSTANT if the metadata record already exists and
  the undo log record for updating it would exceed
  trx_undo_max_rec_size(). trx_undo_page_report_modify() stores the
  DEFAULT value of each column that is being added in that record in
  full, inline. No such limit applies when the metadata record is
  being inserted, because trx_undo_page_report_insert() writes
  TRX_UNDO_INSERT_METADATA and no field data.

- dict_load_table_one():
  If the SYS_TABLES record was written by a transaction that is still
  active, load the table definition as READ UNCOMMITTED. A
  delete-marked record is excluded, because SYS_TABLES.NAME is the
  clustered index key: RENAME TABLE delete-marks the record of the old
  name, and the definition that corresponds to that name is the one
  that precedes the rename.

- dict_sys_tables_rec_read():
  Report whether the current version of the record was written by a
  transaction that has not been committed. The function already
  determines this in order to decide whether to read an older
  version of the record, and used to discard the answer. Store the
  fields that are read in the new type dict_sys_tables_rec, instead
  of in separate output parameters. A caller that does not want a
  delete-marked record to be reported as not found used to indicate
  that by specifying trx_id as nullptr; that is now the parameter
  skip_deleted.

- dict_load_table_low():
  New parameter uncommitted_rec, which is passed on to
  dict_sys_tables_rec_read().

- rec_get_converted_size_comp_prefix_low(),
  rec_convert_dtuple_to_rec_comp():
  For a record that includes a metadata BLOB, determine the number of
  nullable fields from the tuple, by way of
  dict_index_t::get_n_nullable(), and not from
  dict_index_t::n_nullable. This is what
  rec_init_offsets_comp_ordinary() does, and it is equivalent for a
  tuple that comprises all fields of the index. Relax the assertions
  that required the tuple to comprise all of them.

- Added test in innodb.instant_alter and innodb.instant_alter_crash
  to test normal working of INSTANT ALTER, crash safety and full table.
Aleksey Midenkov
MDEV-17613 Refactor prune_partitions() to narrow read_partitions instead of full rewrite

read_partitions is reset to all partitions (or the PARTITION() set) once
per statement in open_table() (set_partition_bitmaps()), and
prune_partitions() only intersects it with lock_partitions and the pruned
set instead of rebuilding it.
mark_all_partitions_as_used() is dropped.

Pure refactoring, results unchanged. It lets a restriction set on
read_partitions before pruning survive it - needed when read_partitions
is pre-restricted, e.g. a versioned table whose history is partitioned
reads only the current partition.
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
Aleksey Midenkov
MDEV-17613 Test cleanup: sorted result for versioning.partition

Avoid result fluctuations with different storage engines.
Monty
Add likely/unlikely to my_b_read/write functions
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
Monty
Remove wrong assert in log_event_server.cc

The test for:
DBUG_ASSERT(thd->lex->query_tables != table_list);
does not make any sense:
- table_list is a pointer we just got from malloc)()
- thd->lex->query_tables can point to an existing TABLE_LIST
  or is a pointer to already freed memory.

In 'main' I got the above assert multiple times triggered when
running rpl.rpl_row_img_blobs on a valgrind build. There was
nothing wrong with the allocations, just random change have
malloc return a recently freed pointer.
Monty
Remove C++ STL machinery from sql/rpl_*info_file.* and CHANGE MASTER parser

Replaces std::function / std::optional / std::unordered_map /
std::unordered_set / std::string_view and template-heavy code in the
master.info / relay-log.info persistence layer with plain MariaDB
idioms.
- On-disk file format unchanged.
- SQL syntrax unchanged
- No notable test changes, except those for which new error messages was
  added and output from SHOW VARIABLES.
- master_* CLI options moved from inline globals in
  rpl_master_info_file.h to Sys_var_* in sys_vars.cc (READ_ONLY,
  AUTO_SET on use_gtid and heartbeat_period). master_heartbeat_period
  is Sys_var_double (seconds); master_use_gtid is Sys_var_enum. Global
  storage sits outside HAVE_REPLICATION so libmariadbd links.
  - This adds the rpl_master_info variables to SHOW VARIABLES
- Value classes are no longer templates. Persistent renamed to Value;
  Optional_* classes renamed (Uint_value_with_default, Path_value,
  etc.).  has_value flag in the base; is_default()
  non-virtual. Multi-line method bodies moved to new rpl_info_file.cc
  and rpl_master_info_file.cc.
- VALUE_LIST (Mem_fn array) and VALUE_MAP (std::unordered_map)
  replaced by per-instance Value *const value_list[] / value_map[]
  arrays + a static TYPELIB populated at first construction from each
  Value's variable_name. seen-set dedup is now a uint32 bitmask.
- Default handling is not anymore done with using special values.
  Setting a value to default is now done by calling set_default(),
  which sets the has_value member to 0 and stores the default value
  in the variable.
  When reading a value, one does not have to check if the value is
  a default value.
  This makes the interface cleaner and easier to manage.
- CHANGE MASTER TO parser no longer builds std::function lambdas:
  LEX_MASTER_INFO carries mi_used_options / mi_used_default bitmasks
  plus plain value fields; change_master() applies them via bit-test +
  direct copy or set_default(). std::optional<T> and trilean are gone
  from the value class API.
- Heartbeat_period_value::load_from uses my_strtod() instead of
  my_decimal for the on-disk format. from_decimal kept for CHANGE
  MASTER.
- RocksDB #if __cplusplus < 201703L guard at the top of
  rpl_master_info_file.h dropped; header now compiles in C++11.
  cstdint dropped; uint32_t / UINT32_MAX in touched code replaced by
  MariaDB's uint32 / UINT_MAX32. include/my_global.h gains
  INT_BUFFER_SIZE next to LONGLONG_BUFFER_SIZE.
- Pointer-style throughout (T * not T &). Multi-line inline functions
  used in more than one place moved to .cc files per style.

New features:
- SHOW VARIABLES now shows the master default values.
- If there is an wrong value for a row in a master.info file the
  corresponding variable name and the value is now written to the
  error log.

Bug fixes uncovered by the refactor:
- master_ssl_verify_server_cert had a pre-existing inconsistency with
  security implications: the documented default and the Sys_var_mybool
  default are TRUE (validate the master's certificate), but the OLD
  init_ssl_config() set the per-Master_info field to FALSE on RESET
  SLAVE ALL. Concretely: after RESET SLAVE ALL followed by a bare
  CHANGE MASTER TO (no explicit MASTER_SSL_VERIFY_SERVER_CERT), the
  slave would connect with MASTER_SSL=1 enabled but with certificate
  validation silently disabled.
  Fixed by not resetting master_ssl_verify_server_cert in
  RESET SLAVE ALL;
- As some replication setups sets master_ssl_verify_server_cert= 0
  while other does not do that, the expected value used in
  check-testcase.inc "Master_SSL_Verify_Server_Cert No" does not make
  sense.  The value of this variable is now ignored by
  check-testcase.inc
- The old code would fail to read a master info file if there was a new
  variable longer than 23 characters. This is now extended to 80.
- Removed warning on RESET SLAVE that "RESET SLAVE is implicitly changing"
  the value of 'Using_Gtid' from 'No' to 'Slave_Pos'" as this change did
  not really happen. This error was given if the master could support
  GTID but the user had specified directly or indirectly to not use GTID.

Other things
- Size of mariadb (optimize build) shrunk with 1,408,009 bytes
  (Based on 'dec' from the size command)
- Size of code in sql directory did not change notable.
- Reworked Master_use_gtid_value to remember users last choice and use
  this as default when connecting to slave.
  Replace mi->using_gtid, with was a pointer to master_use_gtid, with
  two functions:
  - using_gtid() returns the current mode, for the code that needs the
    value itself (SHOW SLAVE STATUS and performance schema output,
    comparisons against CURRENT_POS and SLAVE_POS).
  - is_gtid_supported() tells whether the connection uses gtid at all.
    The many callers that compared the mode against USE_GTID_NO with
    == or != now call this function instead of testing the enum value.
- Master_use_gtid_value::supported() did the same test as
  is_gtid_supported() and is removed
- Removed some operators in rpl_master_info_file.* that hid
  that functions was used to read and update values.

Behavior changes:
- The last used value in CHANGE MASTER for MASTER_USE_GTID will be
  remembered for future CHANGE MASTER for the same connection. This is
  true even if RESET SLAVE is called.
- If Using git is not used with change master but log_file_name, pos,
  relay_log_name or relay_log_pos is specified this is treated as
  if Using_git=NO would have been given. (Old behavior, but not
  documented).
--auto-master-use-gtid does nothing. The default value of
  master_use_gtid is by default SlavePos that will automatically
  change to NO if the master does not support GTID. This already
  handles what auto-master-use-gtid tries to do.
- CHANGE MASTER's MASTER_HEARTBEAT_PERIOD now works like other MASTER
  options:  When set to a value, this value is kept until value is
  changed or set to DEFAULT. The DEFAULT is
  @global.master_heartbeat_period if this is set as startup, if not
  then @global.slave_net_timeout/2 is used.
Monty
Remove wrong assert in log_event_server.cc

The test for:
DBUG_ASSERT(thd->lex->query_tables != table_list);
does not make any sense:
- table_list is a pointer we just got from malloc)()
- thd->lex->query_tables can point to an existing TABLE_LIST
  or is a pointer to already freed memory.

In 'main' I got the above assert multiple times triggered when
running rpl.rpl_row_img_blobs on a valgrind build. There was
nothing wrong with the allocations, just random change have
malloc return a recently freed pointer.
Monty
Remove warnings from aria_chk

- Added right path to datadir where aria_log_control is
- Added option --ignore-control-file as the server is keeping it locked.
Monty
Fixed wrong code in ha_perfschema::delete_table()

In case there was no '/' or '\' in the path, *ptr = '\0' would
change data outside of table_path[]

This patch also removes a warning from gcc.
Monty
Fixed bug in rpl.rpl_killed_ddl.result test case

View 'v1' was not always dropped (timeing issue)
Monty
Add likely/unlikely to my_b_read/write functions
Monty
Fixed wrong code in ha_perfschema::delete_table()

In case there was no '/' or '\' in the path, *ptr = '\0' would
change data outside of table_path[]

This patch also removes a warning from gcc.
Monty
Have mtr also use /usr/lib64/libgalera_smm.so when searching for wsrep lib
Monty
Remove warnings from aria_chk

- Added right path to datadir where aria_log_control is
- Added option --ignore-control-file as the server is keeping it locked.
Aleksey Midenkov
MDEV-17613 Read sole now-partition for non-historical versioned query

When a versioned table stores its history in partitions, an implicit
query (no FOR SYSTEM_TIME) needs only current rows, which all live in
the current partition. vers_setup_conds() no longer adds a row_end
condition for such a table, and prune_partitions() restricts
read_partitions to the current partition instead.

read_partitions is reset to all partitions every statement in
open_table(), so the restriction must be applied on each execution.
prune_partitions() runs on each execution, also for merged derived
tables and views, where vers_setup_conds() is not called again. Both
functions guard with !vers_conditions.was_set(). In prune_partitions()
!is_set() would not do: set_all() sets type (flips is_set()) but not
orig_type (was_set()), so is_set() would be true on prepared-statement /
stored-procedure re-executions and skip the restriction, leaving history
partitions readable - rows matched then grow on each execute/call.

A query whose time comes from @@system_versioning_asof must still read
history, so the restriction is also skipped when from_sysvar is set.
init_from_sysvar() sets this new flag rather than orig_type, so
was_set(), print() and the write lock check still see only an explicit
FOR SYSTEM_TIME. Like orig_type, the flag persists to prepared-statement
re-execution.

EXPLAIN no longer shows "Using where" for such a query: correctness now
comes from partition selection rather than a row_end filter.

With sql_safe_updates, DELETE without WHERE on such a table is now
rejected even with LIMIT, as for a non-versioned table. With an index
on row_end, UPDATE or DELETE without WHERE (UPDATE also without LIMIT)
is now rejected too: it was accepted as a range scan on row_end. A
versioned table without SYSTEM_TIME partitions keeps the old behavior
(MDEV-41393).