expr_lst: expr_lst sub_expr wrapped the expression list parsed so
far in a newly created FTS_AST_LIST for every sub-expression,
so a query such as '(word)' repeated N times built N nested lists.
fts_ast_visit() recurses once per FTS_AST_LIST and overflowed the
thread stack; the default thread_stack was exhausted at about
2300 repetitions.
Append the sub-expression to the list that is already being built,
and create a list only when there is none yet, the way
the neighbouring expr_lst: expr_lst expr rule has always done.
The sub-expressions now become siblings of a single list and
the depth no longer grows.
This also corrects '+term (subexpr)'. The FTS_EXIST pass used to
run to completion in the inner list before the sub-expression was
unioned at the outer level, so rows that matched only the
sub-expression were returned.
Based on mysql/mysql-server@e46e9db484
FILE_OPTIONS_STRING was disabled for 10.11.20 by MDEV-40484.
Use FILE_OPTIONS_ESTRING for the remaining fields (md5,
charset names never contain escaped characters).
btr_page_reorganize_low(): If corruption is found, release the
backup block that had been allocated. This leak was already fixed
in commit fe0c06a232 (MDEV-40408).
Furthermore, restore the contents of the old page in order not
to introduce further corruption.
This was motivated by
mysql/mysql-server@9e497b0773
which seems to aim to do the opposite of
commit c9498f33de (MDEV-18519)
by introducing an intentional crash on corrupted data.
Reviewed by: Alessandro Vetere
Problem:
STR_TO_DATE(str, format) with %., %@ and %# format specifiers could
read past the end of str, because the loops in extract_date_time()
read a byte before checking for the end of str. When str was not
NUL-terminated, that byte was uninitialized, causing the MSAN error.
Fix:
Check for the end of str before reading the next byte.
The test set the password via "chcp 850 && echo ...", but the console
codepage is shared by all parallel mtr workers, so another process could
switch it to UTF8 and the password got stored as UTF8.
Write the cp850 password byte to a file and feed it to the client instead.
CONC-821 in Connector/C 3.3.21 no longer resets *length for string
binds, so for NULL values the length array in append_stmt_result()
stayed uninitialized. Zero-fill it on allocation.
Failure:
- Filesystem flush before transfer only checks symlinks directly under
the data directory, missing a per-table DATA/INDEX DIRECTORY symlink
one level deeper (MyISAM, Aria), and missing InnoDB's per-table DATA
DIRECTORY entirely, since that uses a '.isl' text file, not a symlink.
- The database directory transfer recreates symlinks as symlinks. The
joiner's rsync daemon cannot keep an absolute symlink target as-is,
so a MyISAM or Aria table placed by DATA DIRECTORY or INDEX DIRECTORY
comes out unreadable after the SST.
Fix:
- Extend the flush's symlink search to two levels deep, and add a scan
for '.isl' files, flushing the filesystem each one points to.
- Transfer the database directories with --copy-links instead of
--links, so a table file reached through a symlink is copied by
content instead of recreated as a broken symlink.
Test:
- MDEV-40727.test creates an Aria table with DATA DIRECTORY and INDEX
DIRECTORY and an InnoDB table with DATA DIRECTORY, forces a full SST,
then checks the donor's log for the flush of both external
directories and checksums every table in database test between the
two nodes.
Assisted-by: https://mariadb.org/governance/governance-ai-policy/
The rsync SST donor ran a bare sync() between the table flush and the
transfer. sync() flushes every mounted filesystem on the host, so its
duration depends on I/O unrelated to the SST. The donor holds FTWRL,
LOCK_log and a paused provider at that point, so the whole server is
frozen until sync() returns.
Flush only the filesystems that hold the files to transfer: the data
directory, an absolute binlog directory, and each symlinked database
directory.
Use the full sync() where 'sync -f' (syncfs) is not available.
The test reads innodb_buffer_pool_resize_status while the
SET GLOBAL innodb_buffer_pool_size thread is blocked at
buf_pool_shrink_before_wakeup and expects nothing withdrawn yet:
Withdrawing blocks. (505/505).
But buf_pool_t::resize() sets n_blocks_to_withdraw and
first_to_withdraw and releases buf_pool.mutex before that sync
point. From then on other threads withdraw blocks: allocate() from
any page allocation, buf_LRU_block_free_non_file_page() on eviction,
and the page cleaner LRU batch which evicts pages from the withdrawn
area while to_withdraw is nonzero. So the first counter can be
anything down to 0:
Withdrawing blocks. (0/506).
The fix masks the first counter in replace_regex, so the result
does not depend on how many blocks were already withdrawn.
This does not weaken the test: it still checks that the shrink is
reported while in progress. How many blocks are already withdrawn at
that moment depends only on timing.
initialize counters when constructing a new PFS_program object
The test fails because a newly created routine can get a performance
schema slot freed by a routine dropped in an earlier test. The counters
of a reused slot are not reset, so the new routine shows the old
routine's statistics.
Problem: btr_pcur_t::restore_position() accepts a stored position
optimistically when the block at the stored descriptor address has the
same page id and the same buf_block_t::modify_clock. When
buf_pool_t::resize() shrinks the buffer pool, the withdrawn block
descriptors are freed. When it grows the buffer pool again, the new
descriptors at the same addresses start with modify_clock=0. A cursor
that was stored before the shrink can then find a different instance of
the page with an equal modify_clock, and it restores to a record that
is no longer correct. In a release build, an UPDATE can change a row
that does not match its WHERE condition and skip the rows that match.
Fix: Add buf_pool_t::withdrawn_modify_clock. buf_pool_t::shrink() sets
it to the maximum modify_clock of the blocks that it withdraws.
buf_pool_t::resize() sets the initial modify_clock of each block that
it adds to one more than this. A relocation in shrink() increments only
the modify_clock of the destination block, so a withdrawn block can
keep the value that a cursor stored. Thus the modify_clock at a block
descriptor address never repeats an earlier value, and a stale
optimistic restore fails. The new member uses a hole before
buf_pool_t::flush_list_mutex, so the layout of the other members does
not change.
The test innodb.buf_pool_resize_pcur_restore reads a page into the
first new block after a grow, and makes a stored cursor for a read and
for an UPDATE restore its position across a page reorganization, a
shrink and a grow.
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.
Allocate the context with EVP_CIPHER_CTX_new()/EVP_CIPHER_CTX_free(), and
on an init error free and null it, so a later update()/finish() from a
caller that ignored the error is a safe no-op, not a double free. Remove
check_openssl_compatibility() and the EVP_CIPHER_CTX_SIZE and
EVP_CIPHER_CTX_init macros.
my_md5() used the same stack-buffer-sized-by-EVP_MD_CTX_SIZE hack, whose
only guard was check_openssl_compatibility(). MD5_CTX is a concrete struct
in both OpenSSL and WolfSSL, so use it directly with MD5_Init/Update/Final
on a caller-owned context, like my_sha.inl does for SHA*_CTX; this removes
the EVP_MD_CTX hack and EVP_MD_CTX_SIZE. The exception is OpenSSL 1.0.x,
which aborts on the low-level MD5 API in FIPS mode (MDEV-7788): there, and
only there, keep the EVP path with the non-FIPS flag. 1.1.0 removed the
FIPS Object Module and 3.0+ low-level MD5 bypasses the provider, so the
concrete MD5_CTX is correct on every other version. SHA is unaffected, it
already used concrete SHA*_CTX structs.
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(). The 1.0.x MD5 path verified against a FIPS build of
OpenSSL 1.0.2u: the EVP path computes MD5, the low-level path aborts.
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
parse_v2_header() required only 25 (TCPv4) / 49 (TCPv6) bytes, but then
reads the source port at hdr[25] / hdr[49]. A truncated header could make
it read uninitialized stack.
Require the whole address block: 28 bytes for TCPv4 and 52 for TCPv6.
Also, the declared header length was trusted without checking how many
bytes vio_read() returned. Reject short reads. The spec requires the
sender to send the header at once, and allows the receiver to drop the
connection on a partial header, so a single vio_read() is enough.
The v1 parser had the same problem on EOF: vio_read() returning 0 was
taken for a byte read, so the uninitialized buffer was parsed. Require
exactly one byte.
Extend test_proxy_header_tcp with a header cut short before the port,
and add test_proxy_header_short for a header shorter than it declares.
Light refactoring, to avoid repetition: added connect_tcp() and
init_v2_header(), which are now shared by several proxy header tests.
Assisted-by: Claude:claude-sonnet-5-5
After MDEV-39261 purge opens TABLE via open_purge_table() for any
undo record of a table with indexed virtual columns, including the
insert undo record. If purge does that after FLUSH TABLES, the TABLE
is put back into the table cache. Then DELETE from con1 reuses the
cached TABLE, skips open_table_from_share() and never hits the
TABLE_after_field_clone sync point. DELETE completes immediately,
wait_condition times out and SELECT returns an empty set.
The fix waits for purge to complete before FLUSH TABLES.
versioning.partition (MDEV-41543) and main.grant2 (MDEV-41099) print
row_end of the current row. Since MDEV-32188 in 11.5 the max TIMESTAMP
is 2106-02-07 06:28:15.999999 on 64-bit and 2038-01-19 03:14:07.999999
on 32-bit, so the results recorded on 64-bit fail on 32-bit.
The fix prints CURRENT instead of row_end of the current row in
versioning.partition (using current_row_ts() from common.inc) and
replaces row_end with # in main.grant2, where only the privilege to
read it is tested.
Affected since 11.8. The fix is committed to the originating 10.11 to
minimize conflicts.
Event_job_data::execute() pointed thd->security_ctx at a local
Security_context. A concurrent PROCESSLIST scan captures that pointer
without a lock and can read it after execute() returns.
Make the Security_context a member of Event_job_data, which lives in
Event_worker_thread::run() until after thd is deleted.
Add main.events_debug test
The problem with the test was that it restarted node 2 by writing
"try:--galera-unknown-option" into the expect file. However, MariaDB mtr has
never supported a "try" command. Instead mtr skipped any expect file line not
starting with "restart", and node 2 was not started with the unknown option
(which was the idea in the test logic). The test only slept and then restarted
node 2 normally.
MDEV-40023 made mtr report unknown expect file commands as errors,
so the test now fails in 13.1 and later.
The fix is to start node 2 with the unknown option directly with --exec.
In cluster, the unknown option is found only after the state transfer has started.
The server should interrupt the state transfer and abort.
Galera version 26.4.25 or later is needed for graceful shutdown, and this is now
required by the test.
XmlDB was copied with strcpy/strcat into a 64-byte stack buffer to
build the XPath. Tabname used the same buffer with snprintf, silently
truncating the XPath.
Build the XPath in a buffer allocated by its real length.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
slave_close_thread_tables() unconditionally called
trans_commit_stmt()/trans_rollback_stmt(), which assert
!thd->in_sub_stmt. A BINLOG statement with malformed base64 payload
executed from an AFTER INSERT trigger hits this: on decode failure,
mysql_client_binlog_statement() sets thd->is_error() and calls
slave_close_thread_tables() from within the trigger's sub-statement.
The fix refuses BINLOG in stored routines, triggers, events and
anonymous blocks with ER_SP_BADSTATEMENT at parse time, like LOCK,
HANDLER etc. Dynamic SQL (EXECUTE IMMEDIATE, EXECUTE) run from a
routine bypasses the parser check, so mysql_client_binlog_statement()
checks spcont as well: in a stored procedure in_sub_stmt is 0, but the
decoded events assert !spcont in reset_for_next_command().
1. BINLOG statement executed from a trigger, SF or SP is disabled
by the patch:
Row events (Rows_log_event) open their target table via a
one-shot check that only fires at the top of a fresh statement.
Inside a trigger, the table is never opened this way. It may be
fixed by reusing query_tables, but:
- find_locked_table() matches only by table name -- it could return
the TABLE instance the enclosing statement is actively writing
through, not an idle one. Reusing it would require pre-saving its
state: record[0]/bitmaps/handler/etc. (and would bypass
ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG)
- set_stmt_row_injection()/set_time() calls would mutate thd->lex,
which at that point is main_lex -- shared with the enclosing
statement, not something safe to touch.
Statement events (Query_log_event) run the embedded query via
mysql_parse(): it bundles lex_start(), reset_for_next_command() and
parse_sql() as one unit meant for a genuinely new top-level statement,
not a one-off nested parse.
Calling parse_sql() directly instead avoids that, but then we own
everything mysql_parse() was doing for us: a private LEX and a
private Query_arena (or allocations land on main_lex/whatever arena
is currently active, shared with the enclosing statement), plus
calling mysql_execute_command() ourselves afterwards.
In any case, DML for query_tables cannot be done due to
ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG reasons explained above.
2. PS for BINLOG statement still works and needs a leak fix for
statement events:
A BINLOG statement decoding to a Query_log_event, executed via
PREPARE/EXECUTE, leaked its nested query's allocations onto the PS's
own persistent arena: thd->stmt_arena pointed at it while mysql_parse()
ran the decoded query. Second EXECUTE asserted on ROOT_FLAG_READ_ONLY,
since PROTECT_STATEMENT_MEMROOT marks that arena read-only after a
successful execution.
The fix redirects thd->stmt_arena to thd itself for the duration of
the event application, so stmt_arena->is_conventional() reads
true and activate_stmt_arena_if_needed() (called e.g. from
save_leaf_tables()) never redirects allocations to the PS's arena in
the first place.
Harmless for what it's protecting: leaf_tables_exec is normally cached
on the persistent arena so a repeatedly-executed statement's
SELECT_LEX doesn't rebuild it every time, but our SELECT_LEX is torn
down and reparsed fresh (due to mysql_parse() semantics) on every
EXECUTE, so there's nothing to cache here regardless of which arena is
used.
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.
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.
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.
The test sent
ALTER TABLE t1 ADD COLUMN f3 INTEGER; INSERT INTO t1 VALUES (NULL, 10000, 10000);
from node_2 as a single --send. Nothing ordered that INSERT against the
CREATE UNIQUE INDEX issued from node_1, so the second DDL could reach node_2
while the INSERT was still executing there and BF-abort it. The automatic
autocommit retry then replicated with last_seen_seqno behind the seqno of the
DDL and failed certification against its exclusive key on test.t1. As
wsrep_retry_autocommit defaults to 1 there was no second retry and the client
got ER_LOCK_DEADLOCK.
Send only the ALTER TABLE and execute the INSERT after both DDL statements
have completed on both nodes.
Waiting for the second DDL by polling INFORMATION_SCHEMA.PROCESSLIST did not
prove that it had been ordered either: wsrep_TOI_begin() sets the
"Waiting to execute in isolation" stage before it replicates the writeset, so
that state can be observed before the DDL has reached total order. Wait on
node_2 for the new index to appear instead, which happens only once the DDL
has been ordered behind the parked ALTER TABLE, and record on node_1 that
neither DDL has been applied there while the first one is still parked.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Problem: JSON_TABLE maps JSON numbers [0,1] to BIT(1) values [1,1].
The scalar helper passes decimal text to BIT's binary-string store,
so the byte for '0' is 48 and is clipped to 1.
Fix: Add Field::store_numeric() for number literals serialized as text,
including fractions and exponents, and use it for JSON_TABLE numbers.
BIT parses these literals as rounded unsigned integers and applies its
existing range checks. Other fields retain their existing conversion;
JSON strings and booleans retain their existing storage paths.
Preserve the existing distinction between conversion warnings and JSON
structural errors, so numeric overflow does not invoke ON ERROR.
Extend json.json_table to cover BIT widths, unsigned boundaries,
fractions, exponents, boolean/string/null inputs, other field types,
conversion warnings and ON ERROR responses.
Assisted-by: OpenAI Codex:gpt-6
Issue:
generate-ssl-certs.sh is a /bin/sh script but uses process substitution
(<(printf ...)) for -extfile, which fails with plain sh.
Solution:
Write the extensions to a file in gcadir/ and pass it to -extfile.
Problem: SUM(DISTINCT b'1100') returns 9 instead of 12. The hybrid
hex/bit literal handler uses the default DOUBLE temporary field, so
its byte length becomes a DOUBLE(M,0) display width and clips the
numeric value.
Fix: Override numeric DISTINCT field construction for hybrid hex and
bit literals to allocate an unsigned Field_longlong directly. Preserve
argument-based field selection for other types so DISTINCT retains the
input expression's precision and scale, including decimal AVG inputs.
Extend main.sum_distinct to cover bit and hex literals, zero, byte and
unsigned 64-bit boundaries, duplicates, AVG, COUNT and decimal scale.
Recreated-by: Georgi "Joro" Kodinov <joro@mariadb.org>
When InnoDB computes an indexed virtual column (e.g. the long unique
hash), TABLE::update_virtual_field() walked its expression with
update_vcol_processor() and recomputed every vcol it referenced,
including STORED ones. innobase_allocate_row_for_vcol() saves and
restores Field_blob::value only for non-stored blob vcols, so
recomputing a STORED blob vcol in Field_blob::store_field() did
val_str(&value) and freed the buffer that record[0] still pointed to.
The row-format binlog then packed record[0] from freed memory.
The buffer is allocated only when the value is converted, so the bug
needs a character set conversion (e.g. BLOB to utf8mb3 TEXT).
The fix skips STORED vcols in update_vcol_processor(). Their values are
already in the record: InnoDB copies them as base columns of the
virtual column, and the SQL layer computes them before writing.
The test cases detect the bug only under ASAN or Valgrind.
For a partitioned InnoDB table with AUTO_INCREMENT as the leftmost PK
column, values are issued by the partitioning layer from the shared
counter Partition_share::next_auto_inc_val, reserved in doubling blocks
(1, 2, 4, 8 ... values per reservation).
At end-of-statement, ha_partition::release_auto_increment() returns
unused tail values to the shared counter. When a row fails mid-statement,
handler::restore_auto_increment() rolls next_insert_id back to the
boundary of an earlier block. The guard in release_auto_increment()
inspects only the last interval, so it passes even when the returned
value is already in use by concurrent sessions.
Fix: extend the guard to also verify that next_insert_id is within the
statement's own reservation interval, rejecting a lowering caused by
restore_auto_increment() rolling back across a block boundary.
WiX v3 is out of support. Support legacy WiX v3.x (default) and modern
WiX (wix.exe, newer than v3). WXS sources stay in v3 format, modern WiX
(-DWITH_WIX3=OFF) builds them after on-the-fly 'wix convert'.
Two WXS edits make the converted sources valid: ALLUSERS property is
replaced by InstallScope='perMachine', and ProgramMenuFolder is moved
from under INSTALLDIR to TARGETDIR.
WIX_ACCEPT_EULA=ON accepts the EULA of WiX 7 and later, if WiX asks for it.
A failed WiX step is reported by name.
Custom actions : get rid of wcautil and dutil library dependencies
They are not an official part of WiX distribution anymore.
Replace the functionality with native MSI api, the Wix helpers
we used were thin wrappers anyway.
Also replace the VBScript FakeFailure custom action with a native
one, VBScript is deprecated in Windows and scheduled for removal.
Read MSI properties with a helper that handles any length and reports
errors. Values are no longer silently truncated to MAX_PATH, e.g a long
data directory could be cut to its parent, and then removed.
A custom action that fails to read a property or to call a Windows API
returns ERROR_INSTALL_FAILURE, it was silently ignored before.
When a select containing 32 ranges is made on a table containing a
compound key with 32 parts, the range optimizer can run off the end
of a stack variable, invalidly overwriting subsequent stack variables.
In the struct st_sel_arg_range_seq, we have an array
RANGE_SEQ_ENTRY stack[MAX_REF_PARTS];
MAX_REF_PARTS is 32.
check_quick_select
/ sel_arg_range_seq_init
initialises stack[0] as NOT a key part
/ sel_arg_range_seq_next
iterates through the key parts, adding key part n to stack[n+1]
key part #32 gets referenced by step_down_to(), setting seq->i off the
end of the array.
Fix: RANGE_SEQ_ENTRY stack[MAX_REF_PARTS+1];
The above change exposed an issue with key length calculation on
MS Windows. Calling make_prev_keypart_map(32) caused the resultant
bitmap to be calculated as (1UL << 32) - 1. Using the MSVC compiler
this resulted in an empty key length calculation during
handler::index_read_map, causing an assertion in ha_innobase::index_read().
As we only need 32 bits to represent our key map, we change the type thus
-typedef ulong key_part_map;
+typedef uint32 key_part_map;
We correct make_keypart_map() and make_prev_keypart_map() to call our
overflow safe my_set_bits(). We also correct bka_range_seq_next()
and bkah_range_seq_next() to use make_prev_keypart_map().
We also add some DBUG_ASSERTS in key_part_map processing elsewhere,
exposing some issues in our BNLH implementation. We cap the number of
keyuse parts here, altering the explain output of 2 of our tests.
Numerous places needed bit shift operations altered to use
make*keymap_part and various format strings needed to be corrected.
With --ps-protocol, mysqltest runs a SELECT a second time and compares
the results. The regex ps2_re only matched a leading SELECT, so a
statement starting with '(' was executed only once.
Allow any number of '(', each optionally followed by spaces, before
SELECT in ps2_re.
Compression is stored in unireg_check as Field::TMYSQL_COMPRESSED, but
has_default_function() treated any unireg_check other than Field::NONE as a
default function. A COMPRESSED NOT NULL column without an explicit DEFAULT
therefore never got NO_DEFAULT_VALUE_FLAG, the FRM was written without
FIELDFLAG_NO_DEFAULT, and the column silently became optional while
SHOW CREATE TABLE displayed a phantom DEFAULT ''.
Exclude TMYSQL_COMPRESSED in has_default_function() and use that method in
Column_definition::check() as well, so that both places which set
NO_DEFAULT_VALUE_FLAG stay in sync.
Tables created before the fix keep the wrong implicit DEFAULT '' in their
FRM and are not repaired automatically, the affected columns need an
explicit ALTER TABLE ... MODIFY.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1. Built-in plugins (plugin_init):
A non-ASCII builtin name is a developer error. Added a check
using my_string_repertoire() that prints an error via
print_init_failed_error() and returns 1 to abort server startup.
2. Dynamically loaded plugins (plugin_add):
Added a pre-validation loop before the registration loop. If any
plugin name in the .so is non-ASCII, the entire .so is rejected
before any plugin is registered, preventing partial state where
valid plugins from the same .so get left INACTIVE in the hash.
Added test plugins and MTR tests to verify both rejection of
non-ASCII named plugins and successful loading of ASCII named plugins.
recv_recovery_from_checkpoint_start(): Before applying any page-level
log records by invoking recv_scan_log(true, parser), invoke
recv_rename_files() to replay FILE_RENAME records. In this way,
recv_sys_t::recover_deferred() will not overwrite any old data files
that had been renamed before a new file was created.
recv_sys_t::validate_checkpoint(): Refer to recv_sys.scanned_lsn
rather than recv_sys.lsn, because recv_scan_log(false, parser)
may reset the latter to rewound_lsn in case we run out of memory for
recv_sys.pages. In any case, the second call to recv_scan_log() will
proceed to the end of the log and parse all records.
Reviewed by: Thirunarayanan Balathandayuthapani
Problem:
ALTER TABLE ... DROP PERIOD IF EXISTS FOR can trigger an assertion failure in debug builds and crash in non-debug builds.
Cause:
Lex_cstring_with_compare::streq() may invoke Compare_identifiers on default-constructed Lex_cstring objects (str == NULL, length == 0). Compare_identifiers assumes non-null string pointers and asserts or crashes when called with a null pointer.
Fix:
Add null-pointer handling to Lex_cstring_with_compare::streq() and avoid calling Compare_identifiers when either string pointer is null. This prevents invalid comparisons while preserving existing behavior for initialized strings.
Problem:
Executing a range select using an index that includes virtual/generated columns
(e.g. SELECT ... WHERE c BETWEEN ... ORDER BY d) could trigger:
Assertion 'marked_for_read()' failed in Field access during execution.
Cause:
The keyread initialization path was populating table->read_set using
mark_index_columns(), which follows the non-recursive index marking path.
This only marks direct index fields and does not resolve dependencies of
virtual/generated columns.
As a result, when an index contained virtual columns, their dependent base
columns were not included in read_set, leading to invalid access during
later evaluation of virtual columns.
Fix:
Replace mark_index_columns() with mark_index_columns_for_read() in the
keyread initialization path. This ensures the recursive read-marking logic is
used, so virtual/generated column dependencies are correctly propagated into
read_set before execution.
Problem:
Item_insert_value::fix_fields() created a temporary field
without preserving vcol_info. As a result, VALUE()/VALUES()
on virtual columns lost dependency metadata and could trigger:
Assertion `marked_for_read()' failed during virtual column evaluation.
Cause:
The temporary Field_string copied field_index but did not copy virtual column metadata from the original field.
Fix:
Preserve vcol_info when creating temporary fields in Item_insert_value::fix_fields() so virtual column dependency
tracking remains intact.