Home - Waterfall Grid T-Grid Console Builders Recent Builds Buildslaves Changesources - JSON API - About

Console View


Categories: connectors experimental galera main
Legend:   Passed Failed Warnings Failed Again Running Exception Offline No data

connectors experimental galera main
Aleksey Midenkov
MDEV-25529 ALTER TABLE FORCE syntax improved

Lets an alter_list be supplied alongside a partitioning or
remove_partitioning clause in either order. This allows the FORCE clause
to be added to an existing command, and lets AUTO and FORCE be specified
together, which later commits of this task rely on.
Aleksey Midenkov
MDEV-25529 Comment on partition_element allocation in set_up_default_partitions()

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

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

Rename the error symbol ER_PART_STARTS_BEYOND_INTERVAL to
WARN_VERS_STARTS_BEYOND_INTERVAL and extend its message: it now takes the
STARTS timestamp and the query timestamp as arguments, so the number of
format arguments changes from one to three. The error number (4164) is
unchanged, so this is not an ABI break, but libmariadb still exports the
old symbol name for that number until its submodule is updated.
Aleksey Midenkov
MDEV-25529 Auto-create: Pre-existing historical data is not partitioned as specified by ALTER

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

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

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

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

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

Static key_cmp was renamed to key_eq to resolve compilation after key.h
was included as key_cmp was already declared there.
Aleksey Midenkov
MDEV-41543 Changing INTERVAL of SYSTEM_TIME partitioning does not move history

ALTER TABLE ... PARTITION BY SYSTEM_TIME with a changed INTERVAL or
STARTS on a table already partitioned by SYSTEM_TIME took the fast
ALTER path, which only rewrites the table definition. The history rows
stayed where the old layout put them, so after the ALTER they were in
wrong partitions. An unchanged interval took the copy path instead.

MDEV-29873 replaced memcmp() of the intervals, which is true when they
differ, with interval_t::operator==, which is true when they are equal,
and lost the negation.

The copy path was also broken: it assigned the whole vers_info of the
new partitioning spec to the cloned partition info, now_part and
hist_part included. The ALTER does not list partitions, so now_part
became NULL and check_partition_info() failed with ER_VERS_WRONG_PARTS
for a table with explicit partitions (MDEV-33370 test case).

The fix negates the comparison again (now with interval_t::operator!=),
so a changed interval rebuilds the table and redistributes the history.
It copies only interval, limit and auto_hist from the new spec and keeps
now_part and hist_part of the existing partitions.

Without STARTS the start of the interval defaults to the current time
rounded down to the INTERVAL unit. So ALTER with the same INTERVAL and
no STARTS usually gets a different start and now rebuilds the table.

Vers_part_info::operator=, which copied now_part and hist_part and had
no other callers, is deleted.

Fast ALTER was also chosen when the same statement changed the table
definition, e.g. ADD COLUMN with the same INTERVAL and STARTS, or DROP
COLUMN with LIMIT. It wrote the new .frm, but the partitions kept the
old record layout. Now any change of the table definition
(alter_info->flags) takes the copy path.
Aleksey Midenkov
MDEV-41543 Changing INTERVAL of SYSTEM_TIME partitioning does not move history

ALTER TABLE ... PARTITION BY SYSTEM_TIME with a changed INTERVAL or
STARTS on a table already partitioned by SYSTEM_TIME took the fast
ALTER path, which only rewrites the table definition. The history rows
stayed where the old layout put them, so after the ALTER they were in
wrong partitions. An unchanged interval took the copy path instead.

MDEV-29873 replaced memcmp() of the intervals, which is true when they
differ, with interval_t::operator==, which is true when they are equal,
and lost the negation.

The copy path was also broken: it assigned the whole vers_info of the
new partitioning spec to the cloned partition info, now_part and
hist_part included. The ALTER does not list partitions, so now_part
became NULL and check_partition_info() failed with ER_VERS_WRONG_PARTS
for a table with explicit partitions (MDEV-33370 test case).

The fix negates the comparison again (now with interval_t::operator!=),
so a changed interval rebuilds the table and redistributes the history.
It copies only interval, limit and auto_hist from the new spec and keeps
now_part and hist_part of the existing partitions.

Without STARTS the start of the interval defaults to the current time
rounded down to the INTERVAL unit. So ALTER with the same INTERVAL and
no STARTS usually gets a different start and now rebuilds the table.

Vers_part_info::operator=, which copied now_part and hist_part and had
no other callers, is deleted.
Rex Johnston
MDEV-35845 Let string constants enter multiple equalities for all collations

SELECT * FROM t1 WHERE v IN ('a','b') AND v = 'b' kept both conjuncts
when v is a utf8mb4 column, while the same query on a latin1 column was
simplified to v = 'b'.  BETWEEN and the constant in a join equality
(t1.v = t2.v AND t1.v = 'b') were left alone in the same way.

The rewrite is done by the multiple equality machinery, not by
propagate_cond_constants().  check_simple_equality() turns v = 'b' into
an Item_equal holding the column and the constant.  Later
substitute_for_best_equal_field() walks the remaining predicates, and
Item_func_in::propagate_equal_fields() asks each argument that is a
column of an Item_equal to replace itself with that constant whenever
the predicate compares under the Item_equal's collation.  'b' IN
('a','b') is then constant and remove_eq_conds() folds it away.  The
same path serves BETWEEN, <=> and every other comparison, and
eliminate_item_equal() derives t2.v = 'b' from the join equality.

A constant was only admitted into an Item_equal when the column's
collation handler reported propagate() true.  That flag was added in
2005 for Bug#9509, when a constant taken from an Item_equal was
substituted into any expression, so LENGTH(s1) saw 'oe' instead of the
o-umlaut it equals under latin1_german2_ci.  Every utf8mb3 and utf8mb4
case-insensitive collation and every UCA collation report false, so
once MDEV-19123 made utf8mb4 the default, no string column got a
constant in its Item_equal.  Since MDEV-7152 the substitution is
decided per site by a Context: comparison operators accept the constant
only when their comparison collation is the Item_equal's, everything
else only for binary no-pad collations, and LIKE refuses pad and
non-1-to-1 collations on its own.  The flag therefore duplicated a
finer check, and propagate_cond_constants() had been substituting the
same constants into = and < under the same collations without
consulting it.  Field_str::test_if_equality_guarantees_uniqueness() no
longer consults it either; the remaining tests, that the constant
compares as a string and under the column's own collation, stay.

That exposes one case the flag was hiding.  Charset narrowing
(cset_narrowing) lets a utf8mb3_general_ci column and a
utf8mb4_general_ci column share an Item_equal, so the Item_equal now
also has to hold a utf8mb4 constant for its utf8mb3 member.
eliminate_item_equal() manufactures utf8mb3_col = utf8mb4_const for
that member, which fails collation aggregation when the constant has a
character outside the BMP.  Field_longstr::get_equal_const_item()
converts the constant from the Item_equal's comparison collation into
the member's character set, through the mb4-to-mb3 narrowing charset
when narrowing applies, so that a character outside the BMP becomes
U+FFFD, which is what utf8mb4_general_ci compares it as.  It returns
NULL, and the equality is dropped, when the constant cannot be
represented.  eliminate_item_equal() calls it for every member it
builds an equality for; Item_field::propagate_equal_fields() already
did.

The tests added to func_in.test come from Dave Gosselin's patch for
this issue, which handled IN in propagate_cond_constants() instead.
Two of its expectations change with this approach: an Item_equal prints
its constant first, and v IN ('a',NULL) AND v = 'b' now folds to an
impossible WHERE, as 'b' IN ('a',NULL) is NULL.  The other re-recorded
results show the same reordering, or improved plans where a constant
now reaches another table.

This commit was prepared with Claude Code (Fable 5.1), which traced the
missing rewrite to the propagate() check, showed by experiment that
removing it restores the rewrite and passes the func_in tests, found
the narrowing failure from a query supplied by the author, wrote
Field_longstr::get_equal_const_item() and its call in
eliminate_item_equal(), re-recorded the affected results, and ran the
main suite in normal and --ps-protocol mode before and after.
Dave Gosselin
MDEV-41542:  Wrong results, crash when upgrading to OpenSSL 4.x

Allocates the OpenSSL cipher context of MyCTX with
EVP_CIPHER_CTX_new() and frees it with EVP_CIPHER_CTX_free(), instead
of using a buffer in MyCTX that was zeroed and then passed to
EVP_CIPHER_CTX_reset().

With OpenSSL 4.x, the statement

  SELECT HEX(AES_ENCRYPT('foo', 'bar', '0123456789abcdef',
                        'aes-256-cbc'));

returns NULL instead of 42A3EB91E6DFC40A900D278F99E0726E, failing
the main.func_str test.

EVP_CIPHER_CTX_new() sets the IV length of a new context to -1.
OpenSSL 3.6.5 EVP_CIPHER_CTX_reset() sets it to -1, but OpenSSL 4.0.3
EVP_CIPHER_CTX_reset() leaves a context with no cipher unchanged.
EVP_CipherInit_ex() then fails with an invalid IV length.

my_aes_crypt_init() returns MY_AES_OPENSSL_ERROR when the allocation
fails.  It also destroys the context when init fails, because callers
do not call my_aes_crypt_finish() after a failed init.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Dave Gosselin
MDEV-41146:  Check rand() in WHERE when top level base tables are const

A WHERE conjunct that calls rand() but doesn't refer to a column, such
as rand() < 0, is checked on the last non-const top level base table
in the join order.  When every top level base table was const and the
join order ended with materialized semi-joins, the conjunct was never
checked.  Such a query gave wrong results.  In that scenario the join
returns at most one row, so the conjunct is now checked once before
the join starts, together with the conditions on columns of an outer
query.

The decision uses the same search for the last top level base table
that places the conjunct in every other case.  A join of only const
tables has no such table, and the same test covers it.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Sergei Golubchik
MDEV-41540 filesort assert crash on a huge sort key

sortlength cannot be larger than 4G. If it's reaching the limit, stop
growing the sort key, ignore the tail same as for max_sort_length.

test allocates ~4GB of memory even if very briefly and even if it
doesn't actually use it. So only run it on 64bit, on 32bit let it
fails with "Out of sort memory" - but still no crash.
Aleksey Midenkov
get_next_time() comment
Dave Gosselin
MDEV-41146:  Merge pseudo_bits_cond into outer_ref_cond

A join of only const tables stored the WHERE conjuncts that refer to a
column of an outer query or call rand() in pseudo_bits_cond.  The
executor read pseudo_bits_cond only when the query needed no temporary
table, and read outer_ref_cond otherwise.  A window function or
DISTINCT with ROLLUP needs a temporary table, so those conjuncts were
never checked, and the query returned rows that the WHERE clause
rejects.

pseudo_bits_cond came from the fix for LP bug 908269 in 2012.  For a
join of only const tables, that fix also collected conjuncts that call
rand(), and it stored them in a new field without stating a reason.
The code that filled the two fields and the code that read them chose
between them by the same test.  The tests have differed since
MDEV-8646 in 2016, which added the temporary table condition to the
executor's test.  Later changes only display the field, in EXPLAIN
FORMAT=JSON (MDEV-19714) and in the optimizer trace (MDEV-21095).  In
MDEV-19714, its author wrote that it was not clear why pseudo_bits_cond
was needed in addition to outer_ref_cond.

pseudo_bits_cond is removed, and outer_ref_cond holds these conjuncts
for every join.  The executor checks it on both paths, so where the
conjuncts are stored no longer depends on the executor's test.
Conjuncts that call rand() are still collected only for a join of only
const tables.  EXPLAIN FORMAT=JSON shows these conjuncts as
outer_ref_condition instead of pseudo_bits_condition.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
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.
Brandon Nesterenko
squash! 6e3fd9cbbca55c18f29a62850d0add915dbc71b5

Replace the Master_info::using_gtid pointer with accessor functions.

Master_info kept a pointer into Master_use_gtid_value::active_mode and
all readers accessed the current gtid mode through using_gtid[0]. The
pointer member added an indirection on every check and hid where the
value actually lives. Master_info_file now provides two inline
functions instead, both going directly to the member:

* 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; its only caller now uses the new
function. The conversion of the call sites is mechanical and there is
no change in behavior.

Co-Authored-By: Claude Fable 5 <[email protected]>
Dave Gosselin
MDEV-41542:  Wrong results, crash when upgrading to OpenSSL 4.x

Removes EVP_CIPHER_CTX_SIZE, the EVP_CIPHER_CTX_init() macro of
ssl_compat.h and the part of check_openssl_compatibility() that
measures EVP_CIPHER_CTX_new().  MyCTX was their only user, and it now
allocates its context with EVP_CIPHER_CTX_new() so these are
otherwise unused.

Consequently, mysqld no longer refuses to start when an OpenSSL cipher
context is larger than 200 bytes.  The EVP_MD_CTX check is unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Aleksey Midenkov
MDEV-25529 ErrConvTimestamp for printing timestamps

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

Rename the error symbol ER_PART_STARTS_BEYOND_INTERVAL to
WARN_VERS_STARTS_BEYOND_INTERVAL and extend its message: it now takes the
STARTS timestamp and the query timestamp as arguments, so the number of
format arguments changes from one to three. The error number (4164) is
unchanged, so this is not an ABI break, but libmariadb still exports the
old symbol name for that number until its submodule is updated.
Aleksey Midenkov
MDEV-25529 Fix to_string() OOM return semantics for temporal types

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

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

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

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

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

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

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

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

Static key_cmp was renamed to key_eq to resolve compilation after key.h
was included as key_cmp was already declared there.
Aleksey Midenkov
MDEV-25529 Comment on partition_element allocation in set_up_default_partitions()

partition_element and its push_back() list node are allocated on the
active arena (thd->mem_root). The allocation failure is already reported
there (by my_malloc() or the arena's error handler), which is why the
failure path only returns the error.
Aleksey Midenkov
MDEV-41545 Unchecked get_clone() in ALTER of partitioned table crashes on out-of-memory

get_clone() returns NULL on out-of-memory, but two of its callers in
prep_alter_part_table() dereferenced the result without a check, so the
server crashed:

  changed INTERVAL of SYSTEM_TIME partitioning
  ADD PARTITION under LOCK TABLES

get_clone() itself did not check the allocation of Vers_part_info and
dereferenced it.

handle_alter_part_error() did not check the clone either. The clone only
kept the DDL log entries after the table is closed. In the branch where
DDL log recovery fails, write_log_completed() released the entries
through lpt->part_info, which the table close may have freed, and
release_log_entries() released the same entries again through the
clone. That branch is not reachable now, as ddl_log_execute_entry()
always returns false.

The fix checks the allocation of Vers_part_info in get_clone() and the
result of get_clone() in prep_alter_part_table() like the other callers
there do. handle_alter_part_error() keeps a copy of DDL_LOG_STATE
instead of the clone and passes it to write_log_completed() and
release_log_entries(), which now take DDL_LOG_STATE. The redundant
release_log_entries() after write_log_completed() is removed.
Aleksey Midenkov
MDEV-25529 Extract vers_set_starts() and add starts_clause

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

Also adds the Vers_part_info::starts_clause flag in preparation for later
commits. The flag records whether STARTS was given explicitly (by the
user or in the unpacked frm), which the auto-partitioning code uses to
decide whether to re-align STARTS to the existing history.
Monty
Add likely/unlikely to my_b_read/write functions
Aleksey Midenkov
MDEV-25529 Replace COMBINE macro with interval2sec() and interval2usec()

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

MAX_DAY_NUMBER moves to sql_time.h as a constexpr (the unused copy in
item_timefunc.cc is removed), and the range check of date_add_interval()
becomes the inline interval_days_out_of_range(), so that the sizing can
check that the seconds do not overflow.
Rex Johnston
MDEV-35845 Let string constants enter multiple equalities for all collations

SELECT * FROM t1 WHERE v IN ('a','b') AND v = 'b' kept both conjuncts
when v is a utf8mb4 column, while the same query on a latin1 column was
simplified to v = 'b'.  BETWEEN and the constant in a join equality
(t1.v = t2.v AND t1.v = 'b') were left alone in the same way.

The rewrite is done by the multiple equality machinery, not by
propagate_cond_constants().  check_simple_equality() turns v = 'b' into
an Item_equal holding the column and the constant.  Later
substitute_for_best_equal_field() walks the remaining predicates, and
Item_func_in::propagate_equal_fields() asks each argument that is a
column of an Item_equal to replace itself with that constant whenever
the predicate compares under the Item_equal's collation.  'b' IN
('a','b') is then constant and remove_eq_conds() folds it away.  The
same path serves BETWEEN, <=> and every other comparison, and
eliminate_item_equal() derives t2.v = 'b' from the join equality.

A constant was only admitted into an Item_equal when the column's
collation handler reported propagate() true.  That flag was added in
2005 for Bug#9509, when a constant taken from an Item_equal was
substituted into any expression, so LENGTH(s1) saw 'oe' instead of the
o-umlaut it equals under latin1_german2_ci.  Every utf8mb3 and utf8mb4
case-insensitive collation and every UCA collation report false, so
once MDEV-19123 made utf8mb4 the default, no string column got a
constant in its Item_equal.  Since MDEV-7152 the substitution is
decided per site by a Context: comparison operators accept the constant
only when their comparison collation is the Item_equal's, everything
else only for binary no-pad collations, and LIKE refuses pad and
non-1-to-1 collations on its own.  The flag therefore duplicated a
finer check, and propagate_cond_constants() had been substituting the
same constants into = and < under the same collations without
consulting it.  Field_str::test_if_equality_guarantees_uniqueness() no
longer consults it either; the remaining tests, that the constant
compares as a string and under the column's own collation, stay.

That exposes one case the flag was hiding.  Charset narrowing
(cset_narrowing) lets a utf8mb3_general_ci column and a
utf8mb4_general_ci column share an Item_equal, so the Item_equal now
also has to hold a utf8mb4 constant for its utf8mb3 member.
eliminate_item_equal() manufactures utf8mb3_col = utf8mb4_const for
that member, which fails collation aggregation when the constant has a
character outside the BMP.  Field_longstr::get_equal_const_item()
converts the constant from the Item_equal's comparison collation into
the member's character set, through the mb4-to-mb3 narrowing charset
when narrowing applies, so that a character outside the BMP becomes
U+FFFD, which is what utf8mb4_general_ci compares it as.  It returns
NULL, and the equality is dropped, when the constant cannot be
represented.  eliminate_item_equal() calls it for every member it
builds an equality for; Item_field::propagate_equal_fields() already
did.

The tests added to func_in.test come from Dave Gosselin's patch for
this issue, which handled IN in propagate_cond_constants() instead.
Two of its expectations change with this approach: an Item_equal prints
its constant first, and v IN ('a',NULL) AND v = 'b' now folds to an
impossible WHERE, as 'b' IN ('a',NULL) is NULL.  The other re-recorded
results show the same reordering, or improved plans where a constant
now reaches another table.

This commit was prepared with Claude Code (Fable 5.1), which traced the
missing rewrite to the propagate() check, showed by experiment that
removing it restores the rewrite and passes the func_in tests, found
the narrowing failure from a query supplied by the author, wrote
Field_longstr::get_equal_const_item() and its call in
eliminate_item_equal(), re-recorded the affected results, and ran the
main suite in normal and --ps-protocol mode before and after.
Dave Gosselin
MDEV-41542:  Wrong results, crash when upgrading to OpenSSL 4.x

OpenSSL 4 reports a TLS alert as "tls alert ..." where OpenSSL 3
reports "ssl/tls alert ..." and OpenSSL 1.1.1 reports "sslv3 alert
...".  main.tlsv13 and main.ssl_crl replace the 1.1.1 text with the
3.x text, so they failed when linked against OpenSSL 4.  Both now
replace the 4.x text as well, and the expected results are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Lawrin Novitsky
MDEV-16670 Improve execute direct (prepare and execute) performance

The patch detects if the processed PREPARE command is first part of the
pipelined DIRECT+EXECUTE commands with stmt id -1 for execution. For
that the vio class has been extended with peek method that lets us
verify that the server has already received next command, it is EXECUTE
with stmt id -1, and the packet is minimally complete.

If it is so, the execution is forwarded to the fused prepare+execute path
as if it was a single command. It lets a statement's execute leg run before
any real prepare/check_prepared_statement() has happened and the prepare
leg does basically query parsing only. Several places
downstream assumed that had already happened, so give Query_arena a single
predicate, is_direct_exec_unresolved() - true only for the one-shot fused
pass that never went through a real prepare - and fix each broken
assumption against it:

* Query cache: a cached response is replayed without ever running
  mysql_execute_command(), so the buffered prepare-response hook never
  fires; conversely, populating the cache captures that hook's bytes too
  (net_real_write() sees the whole flushed buffer), poisoning the entry
  for unrelated later clients. Direct-exec statements now opt out of the
  query cache entirely (query_cache_is_applicable /
  lex->safe_to_cache_query, saved and restored around just that one
  execution). Covered by test_mdev_16670_qc_hit_hang and
  test_mdev_16670_qc_poison.

* Reprepare: a mid-execute reprepare() (e.g. ER_NEED_REPREPARE) swaps in
  a lex that DID go through a real prepare, so the statement is no
  longer "unresolved" once that happens - tracked via a new
  IS_DIRECT_EXEC_REPREPARED flag so the direct-exec special-casing only
  applies to the original pass.

* System versioning / periods: skip_setup_conds() treated the fused pass
  as an ordinary re-execution rather than a first resolution, so
  vers_setup_conds()/period_setup_conds() never ran - a versioned table
  or FOR PORTION OF statement used the wrong predicate. Now counted as
  "first execute" via is_direct_exec_unresolved(). Covered by
  test_mdev_16670_versioned_rows and test_mdev_16670_trigger_versioned.

* Unsupported statements check used to be done as part of the
  check_prepared_statement, has been moved to the new
  check_stmt_command_supported function called from the
  Prepared_statement::prepare method. Covered by
  test_mdev_16670_unsupported_ps.

* CALL statements and queries referencing a stored function are excluded
  from the optimized path (skip_direct_exec_optimization()): their nested
  sub-statements run with thd->cur_stmt still pointing at the outer
  statement, which would otherwise misfire the buffered prepare-response
  hook. Also, the stored function use would cause guaranteed re-prepare.
  The flag is reset for them after the query parsing. Covered by
  test_mdev_16670_demoted_function_call.

Also adds regression tests for the reply-pair on parse error
(test_mdev_16670_parse_error_reply_pair), SQLSTATE/errno preservation
through the execute-leg fallback (test_mdev_16670_fallback_sqlstate),
general_log coverage (test_mdev_16670_general_log), a statement killed
mid-fallback (test_mdev_16670_kill_during_fallback), and prebind usage of
mariadb_stmt_execute_direct() with real bound parameters
(test_mdev_16670_prebind_direct).

It's possible to compile with global variable opt_exec_direct that controls
if the optimization should be used. Its existence can be switched on
using cmake option WITH_DIRECT_EXEC_OPTIMIZE_SWITCH. By default it does
not exist, as it is believed to be unneeded in the general case.

create_and_prepare_stmt() helper added to perform common statement creation
routine, and erase_stmt_safely() helper - for cleaning in
create_and_prepare_stmt()'s prepare-failure cleanup, direct_exec_prepare_ok_fallback()'s
OOM branch, and mysqld_stmt_direct_execute()'s packet_error branch.

Assisted-by: Claude Sonnet 5
Aleksey Midenkov
MDEV-41545 Unchecked get_clone() in ALTER of partitioned table crashes on out-of-memory

get_clone() returns NULL on out-of-memory, but two of its callers in
prep_alter_part_table() dereferenced the result without a check, so the
server crashed:

  changed INTERVAL of SYSTEM_TIME partitioning
  ADD PARTITION under LOCK TABLES

get_clone() itself did not check the allocation of Vers_part_info and
dereferenced it.

handle_alter_part_error() did not check the clone either. The clone only
kept the DDL log entries after the table is closed. In the branch where
DDL log recovery fails, write_log_completed() released the entries
through lpt->part_info, which the table close may have freed, and
release_log_entries() released the same entries again through the
clone. That branch is not reachable now, as ddl_log_execute_entry()
always returns false.

The fix checks the allocation of Vers_part_info in get_clone() and the
result of get_clone() in prep_alter_part_table() like the other callers
there do. handle_alter_part_error() keeps a copy of DDL_LOG_STATE
instead of the clone and passes it to write_log_completed() and
release_log_entries(), which now take DDL_LOG_STATE. The redundant
release_log_entries() after write_log_completed() is removed.
Monty
fixup! 0faa995f381086ee2b7105ed1b40a40a04a3ff43
Dave Gosselin
MDEV-41146:  Check rand() in WHERE when top level base tables are const

A WHERE conjunct that calls rand() but doesn't refer to a column, such
as rand() < 0, is checked on the last non-const top level base table
in the join order.  A join of only const tables has no such table and
returns at most one row, so the conjunct is checked once before the
join starts, together with the conditions on columns of an outer
query.

When every top level base table is const and materialized semi-joins
follow them, the join order also has no non-const top level base
table, but only_const_tables() is false.  The conjunct was never
checked, and such a query gave wrong results.

The check before the join now applies whenever the join order has no
non-const top level base table, which is the same condition under
which no table in the join order receives the conjunct.  Such a join
also returns at most one row.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Dave Gosselin
MDEV-41146:  Merge pseudo_bits_cond into outer_ref_cond

A join of only const tables stored the WHERE conjuncts that refer to a
column of an outer query or call rand() in pseudo_bits_cond.  The
executor read pseudo_bits_cond only when the query needed no temporary
table, and read outer_ref_cond otherwise.  A window function or
DISTINCT with ROLLUP needs a temporary table, so those conjuncts were
never checked, and the query returned rows that the WHERE clause
rejects.

pseudo_bits_cond came from the fix for LP bug 908269 in 2012.  For a
join of only const tables, that fix also collected conjuncts that call
rand(), and it stored them in a new field without stating a reason.
The code that filled the two fields and the code that read them chose
between them by the same test.  The tests have differed since
MDEV-8646 in 2016, which added the temporary table condition to the
executor's test.  Later changes only display the field, in EXPLAIN
FORMAT=JSON (MDEV-19714) and in the optimizer trace (MDEV-21095).  In
MDEV-19714, its author wrote that it was not clear why pseudo_bits_cond
was needed in addition to outer_ref_cond.

pseudo_bits_cond is removed, and outer_ref_cond holds these conjuncts
for every join.  The executor checks it on both paths, so where the
conjuncts are stored no longer depends on the executor's test.
Conjuncts that call rand() are still collected only for a join of only
const tables.  EXPLAIN FORMAT=JSON shows these conjuncts as
outer_ref_condition instead of pseudo_bits_condition.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Aleksey Midenkov
MDEV-25176 Shutdown hangs if event scheduler is started during shutdown

SET GLOBAL event_scheduler=1 executed by a connection during shutdown
could start the scheduler thread after close_connections() called
Events::stop(). Nothing stops this thread and close_connections()
waits for it forever.

Now it hangs instead of a crash since MDEV-30260 fix: it moved
deinit() from close_connections() to clean_up(), so the scheduler is
not freed under the running thread anymore.

The fix makes Event_scheduler::start() do nothing when abort_loop is
set. abort_loop is set before close_connections() and is checked under
LOCK_scheduler_state, which stop() also takes, so the scheduler is
either started before stop() and stopped by it, or not started at all.
No error needed: the server is shutting down.
Sergei Golubchik
MDEV-41541 uninitialized memory read in optimizer replay

init all frequencies - not all of them might be present in the replay
Dave Gosselin
MDEV-41146:  Merge pseudo_bits_cond into outer_ref_cond

A join of only const tables stored the WHERE conjuncts that refer to a
column of an outer query or call rand() in pseudo_bits_cond.  The
executor read pseudo_bits_cond only when the query needed no temporary
table, and read outer_ref_cond otherwise.  A window function or
DISTINCT with ROLLUP needs a temporary table, so those conjuncts were
never checked, and the query returned rows that the WHERE clause
rejects.

pseudo_bits_cond came from the fix for LP bug 908269 in 2012.  For a
join of only const tables, that fix also collected conjuncts that call
rand(), and it stored them in a new field without stating a reason.
The code that filled the two fields and the code that read them chose
between them by the same test.  The tests have differed since
MDEV-8646 in 2016, which added the temporary table condition to the
executor's test.  Later changes only display the field, in EXPLAIN
FORMAT=JSON (MDEV-19714) and in the optimizer trace (MDEV-21095).  In
MDEV-19714, its author wrote that it was not clear why pseudo_bits_cond
was needed in addition to outer_ref_cond.

pseudo_bits_cond is removed, and outer_ref_cond holds these conjuncts
for every join.  The executor checks it on both paths, so where the
conjuncts are stored no longer depends on the executor's test.
Conjuncts that call rand() are still collected only for a join of only
const tables.  EXPLAIN FORMAT=JSON shows these conjuncts as
outer_ref_condition instead of pseudo_bits_condition.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Aleksey Midenkov
MDEV-25529 Extract vers_set_starts() and add starts_clause

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

Also adds the Vers_part_info::starts_clause flag in preparation for later
commits. The flag records whether STARTS was given explicitly (by the
user or in the unpacked frm), which the auto-partitioning code uses to
decide whether to re-align STARTS to the existing history.
Dave Gosselin
MDEV-41146:  Check rand() in WHERE when top level base tables are const

A WHERE conjunct that calls rand() but doesn't refer to a column, such
as rand() < 0, is checked on the last non-const top level base table
in the join order.  When every top level base table was const and the
join order ended with materialized semi-joins, the conjunct was never
checked.  Such a query gave wrong results.  In that scenario the join
returns at most one row, so the conjunct is now checked once before
the join starts, together with the conditions on columns of an outer
query.

The decision uses the same search for the last top level base table
that places the conjunct in every other case.  A join of only const
tables has no such table, and the same test covers it.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Aleksey Midenkov
MDEV-25529 Replace COMBINE macro with interval2sec() and interval2usec()

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

MAX_DAY_NUMBER moves to sql_time.h as a constexpr (the unused copy in
item_timefunc.cc is removed), and the range check of date_add_interval()
becomes the inline interval_days_out_of_range(), so that the sizing can
check that the seconds do not overflow.
Monty
squash! 6e3fd9cbbca55c18f29a62850d0add915dbc71b5

- 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.
- Reworked Master_use_gtid_value to remember users last choice and use
  this as default when connecting to slave.
- Changed detection of current gtid state from a hidden function to
  a pointer to a variable.
- 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 Using_git 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.

Reviewer: Kristian Nielsen <[email protected]>
Aleksey Midenkov
MDEV-25529 ALTER TABLE FORCE syntax improved

Lets an alter_list be supplied alongside a partitioning or
remove_partitioning clause in either order. This allows the FORCE clause
to be added to an existing command, and lets AUTO and FORCE be specified
together, which later commits of this task rely on.
Aleksey Midenkov
MDEV-41545 Unchecked get_clone() in ALTER of partitioned table crashes on out-of-memory

get_clone() returns NULL on out-of-memory, but two of its callers in
prep_alter_part_table() dereferenced the result without a check, so the
server crashed:

  changed INTERVAL of SYSTEM_TIME partitioning
  ADD PARTITION under LOCK TABLES

get_clone() itself did not check the allocation of Vers_part_info and
dereferenced it.

handle_alter_part_error() did not check the clone either. The clone only
kept the DDL log entries after the table is closed. In the branch where
DDL log recovery fails, write_log_completed() released the entries
through lpt->part_info, which the table close may have freed, and
release_log_entries() released the same entries again through the
clone. That branch is not reachable now, as ddl_log_execute_entry()
always returns false.

The fix checks the allocation of Vers_part_info in get_clone() and the
result of get_clone() in prep_alter_part_table() like the other callers
there do. handle_alter_part_error() keeps a copy of DDL_LOG_STATE
instead of the clone and passes it to write_log_completed() and
release_log_entries(), which now take DDL_LOG_STATE. The redundant
release_log_entries() after write_log_completed() is removed.
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.

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.