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

Console View


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

connectors experimental galera main
bsrikanth-mariadb
MDEV-36096: Assertion failure in recompute_join_cost_with_limit

An assert was present in method recompute_join_cost_with_limit()
to make sure the recomputed cost is always >= 0.
Although, the assert was correct, it was failing in
CLang compiled versions due to floating point comparison,
when we set  sql_select_limit=1, and
optimizer_join_limit_pref_ratio=1;

In GCC compiled versions, the partial_join_cost was computed to +0.0.
However, in CLang version, the cost turned out to be -0.0.

Changed the assert such that partial_join_cost, would now be checked for
a value >= -DBL_EPSILON. Following it, we set the partial_join_cost to
0, if it has a negative value.
Marko Mäkelä
MDEV-41152: Fix FILE_CREATE recovery

fil_name_process(): Treat FILE_CREATE in the same way as FILE_MODIFY
that led to a FIL_LOAD_DEFER return. Remove the parameter lsn,
and return file_name_t& in which the caller may assign create_lsn
when processing a FILE_CREATE record.

deferred_spaces.reinit_all(): Never create anything for deleted
tablespaces. Doing so could cause a legitimate file to be deleted
if files are being deleted and re-created with the same name.

deferred_spaces::item::lsn: Remove. Starting with
commit 37d8577aee3bd87b5b04464144d064063b169039 (MDEV-40728)
each FILE_ record is parsed only once.

recv_sys_t::parse_store_if_exists(): Tell the caller to skip
tablespaces for which both FILE_CREATE and FILE_DELETE was parsed.
This improves performance, not correctness.

recv_validate_tablespace(): Avoid duplicated tablespace lookup
and remove a redundant deferred_spaces.add(); fil_name_process
already keeps deferred_spaces in sync with recv_spaces.

(cherry picked from commit 12b41e06ade1571d81757abf47cc2f1cd8e8d98f)
bsrikanth-mariadb
MDEV-39868 Wrong result with a window fn over merged derived table column

Problem:
========
A query with a window function over a column of a merged derived table
returns an empty set when another table is joined on a condition over the
same column and is accessed with "Range checked for each record":

  SELECT AVG(subq.c2) OVER (), t2.c1
  FROM t1 STRAIGHT_JOIN (SELECT * FROM t3) AS subq ON t1.c1 = subq.c1
  STRAIGHT_JOIN t2 ON subq.c2 > t2.c1;

With derived_merge=on all the references to subq.c2 are
Item_direct_view_ref objects sharing one underlying Item_field, because
their ref pointers all point into the derived table's field_translation.
Item::split_sum_func2() calls real_item() and puts that shared Item_field
into the list of the window function's temporary table fields, so
create_tmp_field_from_item_field() sets its result_field to a column of
the temporary table.

Item_field::val_int() reads field, but Item_field::save_in_field() reads
result_field, so the two now return different values. The join condition
is evaluated through the same Item_field, and the runtime range analysis
in Field::get_mm_leaf_int() uses save_in_field_no_warnings(). It reads the
still empty temporary table column instead of the value of t3.c2, treats
the value as NULL, and builds a SEL_TREE::IMPOSSIBLE. Table t2 then
produces no rows.

Solution:
=========
Do not unwrap Item_direct_view_ref in Item::split_sum_func2(). The wrapper
is created per reference and is not shared, so the temporary table field
is attached to the wrapper alone and the conditions that refer to the same
view column keep reading the base table field.

Item_ref::create_tmp_field_ex() already creates the same temporary table
field for a view ref over a column, and change_to_use_tmp_fields() already
handles REF_ITEM, so no other change is needed. Ref access was never
affected: get_store_key() takes real_item()->field explicitly.
Marko Mäkelä
MDEV-41152: Fix FILE_CREATE recovery

fil_name_process(): Treat FILE_CREATE in the same way as FILE_MODIFY
that led to a FIL_LOAD_DEFER return. Remove the parameter lsn,
and return file_name_t& in which the caller may assign create_lsn
when processing a FILE_CREATE record.

deferred_spaces.reinit_all(): Never create anything for deleted
tablespaces. Doing so could cause a legitimate file to be deleted
if files are being deleted and re-created with the same name.

deferred_space.create(): Remove some duplicated code. Missing
tablespace files will be created in fil_node_open_file_low()
starting with
commit 759e3523e3d832b174cf0a612704da38b2557b40 (MDEV-38026).

deferred_spaces::item::lsn: Remove. Starting with
commit 37d8577aee3bd87b5b04464144d064063b169039 (MDEV-40728)
each FILE_ record is parsed only once.

recv_sys_t::parse_store_if_exists(): Tell the caller to skip
tablespaces for which both FILE_CREATE and FILE_DELETE was parsed.
This improves performance, not correctness.

recv_validate_tablespace(): Avoid duplicated tablespace lookup
and remove a redundant deferred_spaces.add(); fil_name_process
already keeps deferred_spaces in sync with recv_spaces.

fil_space_t::rename(): If !log, assert !replace and that
the target file name does not exist.

os_file_rename_func(): Do not check that the target path
does not exist. This is already checked by every caller.
This fixes a debug assertion failure that could otherwise
occur when recovering from a crash in
fil_space_t::rename() between the write of the FILE_RENAME
and the actual rename.
Aleksey Midenkov
Clean up comment in binlog_base64_flag.test

Remove unnecessary comment from test file

Co-authored-by: Copilot Autofix powered by AI <[email protected]>
Mohammad Tafzeel Shams
MDEV-35154 : dict_sys_t::load_table() is holding exclusive dict_sys.latch
for unnecessarily long time

Issue:

dict_load_table_one() was invoked with the exclusive dict_sys.latch held
and never released it. The whole load ran inside that one critical
section: reading the SYS_TABLES record, opening the .ibd file, and
reading SYS_COLUMNS, SYS_VIRTUAL, SYS_INDEXES, SYS_FIELDS, the clustered
index root page, and SYS_FOREIGN, as well as loading every table related
by FOREIGN KEY constraints.

Almost all of that is I/O. Because dict_sys.latch is the single latch
that guards every dictionary lookup, one cold table open blocked every
other session from opening any table, including tables that were already
cached. A slow enough load could trigger the fatal semaphore wait check.

Fix:

Split the load into three phases. A short latched phase creates the
table object and publishes it as an incomplete "stub"; the I/O runs with
no latch held; a final latched phase links the FOREIGN KEY constraints.

The stub's progress is held in the top two bits of dict_table_t::
n_ref_count (LOADING_DEF, LOADING_FK, LOAD_FAILED), analogous to how
buf_page_t::state() combines a small lifecycle state with the buffer-fix
count in one atomic. A thread that finds a loading table waits by
pinning it (dict_table_t::try_pin_for_wait(), so it cannot be freed while
unlatched) and acquiring load_latch in shared mode. A failed load can be
torn down safely even though other threads may have already taken such a
pin to wait on it: try_pin_for_wait() refuses once LOAD_FAILED is set,
and the teardown path drains any pins taken earlier before freeing the
stub.

A table remains hidden (LOADING_FK) until every table related to it by
FOREIGN KEY constraints has been loaded as well, and dict_sys_t::
load_table() makes them all visible in one step. The intermediate state
is visible to constraint linking via dict_sys_t::find_table_fk(), so that
two threads loading tables that reference each other can still link the
constraint between them.

Tables that InnoDB internal SQL can refer to are loaded without ever
releasing the latch. The internal SQL parser is not reentrant and is
serialized only by the exclusive dict_sys.latch, and it opens tables in
the middle of parsing; releasing the latch there let another thread run
the parser concurrently and corrupt its state.

Changes:

-  dict_table_t : hold the progress of loading in the top bits of
  n_ref_count (std::atomic<uint32_t>): LOADING_DEF, LOADING_FK,
  LOAD_FAILED, loading(). Add load_latch (separate from lock_latch)
  with load_latch_init()/destroy()/x_lock()/x_unlock()/s_lock()/
  s_unlock(), and the loader-only helpers start_loading(),
  advance_to_loading_fk(), finish_loading(), mark_load_failed(), plus
  try_pin_for_wait() for waiters. acquire()/release() are
  unconditional. Add debug load_thread and is_loader().

-  dict_load_table_one() : publish the table as a LOADING_DEF stub in
  table_non_LRU and release the latch before loading the tablespace,
  the columns and the indexes; reacquire it, move the table to
  table_LRU and advance it to LOADING_FK before loading the foreign
  key constraints. Failures go through dict_load_table_one_discard().
  Add the hold_latch parameter and the dict_load_table_one_no_latch
  debug sync point.

-  dict_sys_t::load_table() : wait for a concurrent load of the same
  table via wait_for_load(); allocate the foreign key table names on
  a local heap, because the latch is released while draining them;
  in the drain loop, wait for a table whose definition is still being
  loaded by another thread and skip one that is only waiting for its
  own related tables; make all tables loaded by this invocation
  visible in one step, releasing each one's load_latch.

-  dict_sys_t::wait_for_load() : pins the observed table via
  try_pin_for_wait() (returning immediately if the load already
  failed), releases the exclusive dict_sys.latch, blocks on the
  table's own load_latch, unpins, and reacquires the latch.

-  dict_load_table_one_discard() : used wherever a stub must be torn
  down (retry after DB_SUCCESS_LOCKED_REC, column or virtual-column
  load failure, a corrupted index or missing FK index). Marks the
  stub LOAD_FAILED, releases load_latch to wake any already-pinned
  waiters, drains the reference count to zero, then removes the stub.

-  dict_sys_t::add() : initialise load_latch for every table, and
  take it in exclusive mode on the loader's behalf before a loading
  stub becomes reachable via find_table_any().

-  dict_sys_t::remove() : also destroy load_latch.

-  dict_load_hold_latch() : whether a table may be referenced by
  InnoDB internal SQL, and therefore must be loaded without releasing
  the latch: InnoDB system tables, FULLTEXT INDEX auxiliary tables and
  the persistent statistics tables.

-  dict_load_table_on_id() : copy the table name and release the
  SYS_TABLES page latch before calling load_table(), which may now
  block. Restore the cursor position only if the scan has to continue.

-  dict_load_foreign(), dict_load_foreigns() : add the fk_heap
  parameter and allocate the names appended to fk_tables on it.

-  dict_sys_t::find_table_any() : the previous body of find_table(),
  returning tables that are being loaded. Only the name is read.

-  dict_sys_t::find_table() : hide tables that are being loaded, both
  in the by-name and the by-id variant. In the by-id variant, loading
  is checked before any bit-field, to avoid a torn read.

-  dict_sys_t::find_table_fk() : like find_table(), but LOADING_FK
  tables are returned, so that constraints can be linked into them
  while holding the exclusive latch.

-  dict_table_can_be_evicted() : a table that is being loaded may only
  be freed by the thread that is loading it.

-  dict_foreign_add_to_cache() : resolve both sides with
  find_table_fk().

-  btr_search_disable() : skip tables that are being loaded. Walking
  their indexes would race with the loading thread, which appends to
  that list without holding the latch. Such tables cannot have any
  adaptive hash index references.

-  create_table_info_t::create_foreign_keys() : take a temporary
  reference on a referenced table as soon as it is resolved, released
  on every exit path by a scope guard; call dict_sys.prevent_eviction()
  only once the constraint is actually committed to the dictionary
  cache, replacing the temporary reference.

-  create_table_info_t::create_table() : acquire a reference to the
  created table around the foreign key handling, and use a local heap
  for the names of the foreign key related tables.

-  row_rename_table_for_mysql() : use one local heap for both the
  dropped-constraint names and the foreign key table names.

-  assertion fix : the load path may now run without dict_sys.latch,
  but only in the thread that is loading the table, and a cached
  table is no longer necessarily fully loaded. dict_sys.locked() is
  relaxed to "dict_sys.locked() || table->is_loader()" in
  dict_load_columns(), dict_load_virtual_col(), dict_load_fields(),
  dict_load_indexes(), dict_index_add_to_cache(),
  dict_index_find_cols(), dict_index_build_internal_clust(),
  dict_index_build_internal_non_clust() and
  dict_index_build_internal_fts(). Checks of dict_table_t::cached are
  relaxed and reordered after the atomic loading in
  dict_table_add_system_columns(), dict_sys_t::add(),
  dict_table_rename_in_cache() and hash_insert().

-  innodb.dict_load_concurrent
  A load is parked at dict_load_table_one_no_latch while
  holding no latch; another table can be loaded meanwhile, and a
  second opener of the same table waits. A second case checks that a
  table is not made visible while a table related to it by a FOREIGN
  KEY constraint is still being loaded by another thread.
Marko Mäkelä
MDEV-41166 --backup --innodb-log-checkpoint-now may copy too much

xtrabackup_backup_func(): Request for a checkpoint synchronously
so that recv_sys.find_checkpoint() will observe the effect.
Alessandro Vetere
MDEV-41201 LRUItr::start(): fix inverted reset condition

The scan hand pointer m_hp must stay in place while it is still
inside the old (cold) sublist of the LRU list, and rewind to the
tail only once it has advanced past the old/young boundary into the
young (hot) sublist. The condition was inverted, so start() rewound
the pointer to the tail on every call instead of only at that
boundary, defeating the intended scan-resumption optimization and
biasing scans toward the tail of the old sublist.

Port of percona/percona-server@dc344fba7e3d272ac18e8d6589b37c678f1e1ad5
by Paweł Olchawa (PS-11446).
Yuchen Pei
MDEV-39525 [to-squash] fix longstr supertype check

The string supertype check is too strict: it bails when src (resp.
dst) is length-limited in chars (i.e. CHAR/VARCHAR) and dst (resp.
src) is length-limited in octets (i.e. TEXT/BLOB).

We change this to conversion of src length limit to that of dst, i.e.

- if src limit is declared in octets and dst limit in chars, convert
  src limit to be in chars, and compare
- if src limit is declared in chars and dst limit in octets, convert
  src limit to be in octets, and compare

This restores the JSON_EXTRACT cases in vcol.vcol_sargable
bsrikanth-mariadb
MDEV-39868 Wrong result with a window fn over merged derived table column

Problem:
========
A query with a window function over a column of a merged derived table
returns an empty set when another table is joined on a condition over the
same column and is accessed with "Range checked for each record":

  SELECT AVG(subq.c2) OVER (), t2.c1
  FROM t1 STRAIGHT_JOIN (SELECT * FROM t3) AS subq ON t1.c1 = subq.c1
  STRAIGHT_JOIN t2 ON subq.c2 > t2.c1;

With derived_merge=on all the references to subq.c2 are
Item_direct_view_ref objects sharing one underlying Item_field, because
their ref pointers all point into the derived table's field_translation.
Item::split_sum_func2() calls real_item() and puts that shared Item_field
into the list of the window function's temporary table fields, so
create_tmp_field_from_item_field() sets its result_field to a column of
the temporary table.

Item_field::val_int() reads field, but Item_field::save_in_field() reads
result_field, so the two now return different values. The join condition
is evaluated through the same Item_field, and the runtime range analysis
in Field::get_mm_leaf_int() uses save_in_field_no_warnings(). It reads the
still empty temporary table column instead of the value of t3.c2, treats
the value as NULL, and builds a SEL_TREE::IMPOSSIBLE. Table t2 then
produces no rows.

Solution:
=========
Do not unwrap Item_direct_view_ref in Item::split_sum_func2(). The wrapper
is created per reference and is not shared, so the temporary table field
is attached to the wrapper alone and the conditions that refer to the same
view column keep reading the base table field.

Item_ref::create_tmp_field_ex() already creates the same temporary table
field for a view ref over a column, and change_to_use_tmp_fields() already
handles REF_ITEM, so no other change is needed. Ref access was never
affected: get_store_key() takes real_item()->field explicitly.
Marko Mäkelä
WIP: log tracking BACKUP SERVER TO ... CONCURRENT (for HAVE_INNODB_PMEM)

backup_sink::id: The thread identifier (0 to CONCURRENT-1)

innodb_backup_checkpoint_pmem(): Copy the old log file.

InnoDB_backup::log_track(), InnoDB_backup::log_track_pmem():
Keep copying the log until we run out of InnoDB data files to copy.

InnoDB_backup::checkpoint_complete_pmem(): Copy the remaining
part of an old log file right before it is being released.

InnoDB_backup::commit(): In log tracking backup, copy the rest of
the HAVE_INNODB_PMEM log.

FIXME: Implement the non-PMEM code path with minimal blocking.
Oleksandr Byelkin
MDEV-39993 Use CREATE OR REPLACE for sys schema routines

mariadb-upgrade silently dropped EXECUTE grants on sys schema stored
functions and procedures. The sys schema install scripts reinstalled
every routine with DROP FUNCTION/PROCEDURE IF EXISTS followed by
CREATE. DROP cascades to delete the routine's rows in
mysql.procs_priv, so any EXECUTE grant a DBA had issued on e.g.
sys.table_exists or sys.quote_identifier was lost every time
mariadb-upgrade reinstalled the sys schema, even though the routine
itself came back unchanged.

Fix: replace DROP ... IF EXISTS + CREATE with CREATE OR REPLACE in
all 53 sys_schema function/procedure files and in the two templates
(templates/function.sql, templates/procedure.sql) so future routines
follow the same pattern. CREATE OR REPLACE PROCEDURE/FUNCTION goes
through sp_drop_routine_internal(), which only deletes the
mysql.proc row and never reaches sp_revoke_privileges() (that is
only called from the explicit DROP PROCEDURE/FUNCTION statement),
so mysql.procs_priv is left untouched and existing grants survive.
This mirrors the pattern already used by sys schema views
(CREATE OR REPLACE ... VIEW, since MDEV-9077), which never had this
problem.

Four of the converted files (functions/format_path.sql,
functions/ps_is_account_enabled_57.sql,
procedures/ps_setup_reset_to_default.sql,
procedures/ps_trace_thread_57.sql) are not referenced by
scripts/sys_schema/CMakeLists.txt; they were converted anyway for
consistency and have no behavioural effect.

As a side effect, a pre-existing UDF whose name collides with a sys
routine name is no longer destroyed before the reinstall fails:
DROP FUNCTION IF EXISTS resolved the UDF namespace first, silently
dropping the UDF and then failing on ER_SP_ALREADY_EXISTS anyway;
CREATE OR REPLACE fails immediately on ER_UDF_EXISTS with the UDF
intact.

scripts/maria_add_gis_sp.sql.in and the sys_config triggers were
deliberately left untouched: the GIS procedures are only
re-installed at bootstrap time (mariadb-upgrade instead patches
their definer in place via UPDATE), and triggers carry no
procs_priv rows, so neither is on the code path this bug is about.

Added mysql-test/main/mysql_upgrade_sys_routine_grants.test, which
grants EXECUTE on sys.table_exists and sys.quote_identifier,
overwrites both routine bodies with a marker to prove the upgrade
actually reinstalls them (rather than the sys schema install being
skipped), runs mariadb-upgrade, and checks both that the grants
survived and that the real routine bodies came back.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Aleksey Midenkov
MDEV-39074 trans_rollback_stmt(THD *): Assertion `! thd->in_sub_stmt' failed.

1. Row-based events work for already locked tables.

slave_close_thread_tables() unconditionally called
trans_commit_stmt()/trans_rollback_stmt(), both of which cannot be
done under sub-statment.

In test case a BINLOG statement with malformed base64 payload executed
from an AFTER INSERT trigger hit this: mysql_client_binlog_statement()
sets thd->is_error() on decode failure and calls
slave_close_thread_tables(), which then asserted since the trigger
body runs as a sub-statement.

The fix guards the commit/rollback with !thd->in_sub_stmt, matching
the same idiom already used in mysql_execute_command() and
open_and_lock_tables() for the same reason: sub-statements defer
statement-transaction finalization to the enclosing top-level
statement. Other callers of slave_close_thread_tables() run only from
the top-level SQL slave applier thread, so this doesn't change their
behavior.

rows_event_stmt_cleanup() has its own trans_commit_stmt()/
trans_rollback_stmt() call after applying a row event; guard it the
same way.

Rows_log_event::do_apply_event() opens its target tables via a
one-shot "if (!thd->lock)" check that only fires at the top of a fresh
statement. Inside a trigger, thd->lock already belongs to the
enclosing DML, so the table is never opened, leaving a NULL TABLE*
used further down. Look it up among the enclosing statement's already
open tables (find_locked_table()) instead, and raise
ER_TABLE_NOT_LOCKED if not found -- the same error a normal
trigger-body statement gets for referencing an unprelocked table.

2. Statement-based events are disabled.

A BINLOG statement decoding to a Query_log_event, executed from within
a trigger or stored routine, cannot be made to work easily.

DML for statement event in trigger cannot be done for new tables
because the locking must be done at once and it cannot be done for
query tables because of ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG.

- Query_log_event::do_apply_event() runs the embedded query via
  mysql_parse(), which assumes it starts a genuinely new top-level
  statement (THD::reset_for_next_command() asserts !spcont/!in_sub_stmt
  and documents itself as "not called by substatements of routines";
  lex_start() reinitializes thd->main_lex, which the general
  BINLOG-statement code reuses as scratch space on the assumption that
  it is idle -- false while the enclosing statement is still running).
  This part is fixable: parse into a private LEX and a private
  Query_arena instead of going through mysql_parse(), the same way
  mysql_make_view() parses embedded SQL text mid-statement.

- What isn't easily fixable is resolving the tables the embedded query
  references. Prelocking is computed statically from the trigger
  body's own SQL text; a table name decoded at runtime from a BINLOG
  payload can never be part of it. open_table(), in prelocked mode,
  only treats an already-open table as reusable when its query_id is 0
  (free) -- a table still owned by the not-yet-finished enclosing
  statement is refused (ER_NO_SUCH_TABLE), and lock_tables() applies
  the same kind of check for a table the trigger writes back into
  (ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG). Bypassing that check
  (reusing the same TABLE object instead of a second, prelocked
  instance) removes the very guard that stops a trigger recursing into
  itself: a trigger on t1 whose BINLOG payload inserts into t1 recursed
  without limit, surfacing not as controlled recursion but as
  save_restore_context_apply_event()'s "!rli->mi" assert, since that
  scratch slot on the shared, fake rli is not reentrant.

Refuse the combination outright instead: Query_log_event::do_apply_event()
now raises ER_SP_BADSTATEMENT when thd->in_sub_stmt, before touching
any THD state.

3. Query_log_event PS execute leak fix

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 nested mysql_parse() call, 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.
Yuchen Pei
MDEV-39525 [to-squash] Check the constant is strictly inside the field domain

The "both bounded" check in vcol_type_both_bounded() used
Field::is_supertype() for the constant as well. That is imprecise in both
directions:

- It compares declared data types, and Item_int always reports INT or
  BIGINT, so for a TINYINT/SMALLINT/MEDIUMINT vcol the check never
  passes, not even for `tinyint_vcol_expr = 2`.

- Being representable in the field's data type is not enough. Field::store()
  maps out-of-domain values onto the extreme values of the domain: numbers
  are clamped, strings are truncated. So a comparison against a constant
  survives the conversion only if no other value can be mapped onto the
  constant, that is, only if the constant lies strictly inside the domain.
  For `a tinyint, va tinyint as (a+1)` and a row with a=127, the expression
  is 128 while va is clamped to 127, so both `a+1 > 127` and `a+1 = 127`
  give different results before and after the substitution.

Add Field::is_const_strictly_inside_domain(), which stores the constant
into the field and checks that
- the value was stored exactly (as the range optimizer does in
  Field::get_mm_leaf_int() and friends), and
- the stored value is not an extreme value of the domain.

The second part is a per-family virtual: integer fields compare against
Type_limits_int, string fields compare the length of the stored value
against the capacity limit, in the unit in which the data type declares it
(characters for CHAR/VARCHAR, octets for BLOB/TEXT). CHAR fields under a
NO PAD collation are excluded altogether, as the store pads the value with
spaces. Data types without an override keep returning false, which only
means a missed optimization.

Also expand eligible Item_bool_rowready_func2 to include "<", "<=",
">" and ">=".

This restores explain results with int in vcol.vcol_sargable cases.

Note, Field::is_supertype() is still needed for the vcol field against the
vcol expression: it covers the case when the conversion is not lossy at
all, including a constant sitting exactly on the boundary of the domain.

Co-Authored-By: Claude Opus 5 <[email protected]>
Thirunarayanan Balathandayuthapani
MDEV-28730 fixup: clang -Wunused-but-set-global
Oleksandr Byelkin
fix ps2 protocol consequences
Rucha Deodhar
MDEV-40490: SET NEW = (row subquery) in a trigger silently assigns NULL
to all columns and skips the single-row check

Analysis:
Assigning a multi-column subquery in a row trigger was resulting in NULL
values because the Item_cache objects wrapped around the columns weren't
being evaluated yet.

Fix:
Calling bring_value() on the RHS item forces the subquery to run and
populate those caches properly before we try to assign them, fixing the
silent data loss.
Alexander Barkov
MDEV-39563 Implement UPDATE ... RETURNING ... INTO

Adding support for UPDATE .. RETURNING .. INTO queries.

For example:

  UPDATE t1 SET a=10,b=20 RETURNING a,b INTO va,vb;
  UPDATE t1 SET a=10,b=20 RETURNING a,b INTO @a,@b;

Limitations:
1. These types of queries:
  - REPLACE .. RETURNING .. INTO
  - DELETE .. RETURNING .. INTO
  - INSERT .. RETURNING .. INTO
  do not work - they return an error.
  They will be implemented separately, when needed.

2. UPDATE..RETURNING..INTO with --binlog_format=statement is not allowed
  and an error is raised.

3. Using OLD_VALUE(col) inside UPDATE..RETURNING..INTO is not allowed
  and an error is raised.

4. Multi-table updates, as well as single table updates with a subquery
  to the same table in WHERE (which get converted to multi-table) do
  not work and an error is raised.

Notes:

1. ANALYZE and EXPLAIN
  Both
    ANALYZE UPDATE .. RETURNING .. INTO ..
    EXPLAIN UPDATE .. RETURNING .. INTO ..
  return this error:
    'RETURNING..INTO' is not allowed in this context

2. Behavior on no data

  a. In case of degenerated plans (WHERE 1=0, LIMIT 0),
    no errors are raised.

  b. If the updated table contains no rows, the behavior depends on the engine,
    for example:
    - MyISAM returns no errors
    - InnoDB raises
        No data - zero rows fetched, selected, or processed
    This behavior is engine dependent because some engines (e.g. MyISAM)
    quickly know that the table has no records and execute the statement
    using a degenerated plan.

  c. If there are some rows, but non of them match the WHERE condition,
    then this error is raised:
      No data - zero rows fetched, selected, or processed

  d. If some rows where found but none of them actually
    got changed by the SET, still this error is raised:
      No data - zero rows fetched, selected, or processed
    The error message might be misleading. However, if we read
    it as "zero rows [that required updates] fetched", it looks OK.
    Let's not introduce a new error message for now.

Helper changes:

1. The grammar in analyze_stmt_command was changed to have
  LEX::analyze_stmt set to true earlier, so
  LEX::set_returning_into_result() already knows if this
  is an ANALYZE statement.

2. The Sql_cmd_update constructor is now called earlier in the grammar,
  to be able to call Sql_cmd_update::set_with_old_value_items()
  in the SET and RETURNING clauses.

3. Sql_cmd_dml::lex is now set during the constructor time.
  It makes things easier:
  - Sql_cmd_update::returns_result_set() needs the lex.
  - Sql_cmd_delete::orig_multitable and Sql_cmd_update::orig_multitable
    are not needed any more.
    They were used only in Sql_cmd_delete::sql_command_code() and
    Sql_cmd_update::sql_command_code().
    Sql_cmd_dml::sql_command_code() now returns lex->sql_command.
    The overrides Sql_cmd_delete::sql_command_code() and
    Sql_cmd_update::sql_command_code() were removed.
bsrikanth-mariadb
MDEV-36356 Server crash in Item::save_int_in_field with Window functions

A tail (ORDER BY / LIMIT / locking clause) that follows a parenthesized
query expression is parsed while the select inside the parentheses is
the current one, so everything the tail registers - its window
functions, the window specifications they introduce and the units of
its subqueries - ends up registered in that inner select.

When the parenthesized query expression already has a tail of its own,
LEX::add_tail_to_query_expression_body_ext_parens() wraps it into a
derived table and attaches the new tail to the wrapping select. The
registrations were left behind in the inner select, so the ORDER BY of
the wrapping select contained window function items that no select had
registered: they never got a Window_funcs_sort, were never computed,
and their result field was read unset. The subqueries of the tail kept
the inner select as their master and name resolution context.

Move these registrations to the wrapping select in the new
move_tail_registrations():

- The window functions of the tail are the ones its ORDER BY items
  contain, looked up with Item::walk() and walk_subquery == FALSE so
  that a window function belonging to a subquery of the tail is not
  found and stays registered where it was parsed. Each one takes along
  the window specification it introduced; "OVER win_name" has none of
  its own, it is looked up by name at fix_fields() time and never
  reaches window_specs.

- Item::walk() does not descend into a window specification, so the
  items of a moved specification's PARTITION BY and ORDER BY lists are
  not re-targeted by Lex_order_limit_lock::set_to() and are pointed at
  the wrapping select's name resolution context here.

- The subquery units of the tail are the ones registered in front of
  the first unit the inner select had when the tail started to be
  parsed; the grammar remembers it in a mid-rule action. There is no
  equivalent of the walk above for them: a unit records neither the
  clause it came from nor its position in the parsed text. They are
  excluded from the inner select, re-registered in the wrapping select
  and their Item_subselect::parent_select is updated.

Per-select parse state that is not a registration (n_sum_items,
with_sum_func, with_rownum, ftfunc_list, uncacheable, and the
select_n_where_fields of the tail's own fields) is deliberately left
behind, where it is merely over-counted.

Also remove the unreachable !unit check - unit is dereferenced on
entry - and fix the indentation of the surrounding block.
Yuchen Pei
MDEV-39525 [to-squash] Move collation match check outside of longstr is_supertype

This allows WHERE json_unquote(json_extract(...))=<const_string> to
use compare_collation instead.
Oleksandr Byelkin
fix embedded server
Oleksandr Byelkin
fix tests
Alessandro Vetere
MDEV-41201 LRUItr::start(): fix inverted reset condition

The scan hand pointer m_hp must stay in place while it is still
inside the old (cold) sublist of the LRU list, and rewind to the
tail only once it has advanced past the old/young boundary into the
young (hot) sublist. The condition was inverted, so start() rewound
the pointer to the tail on every call instead of only at that
boundary, defeating the intended scan-resumption optimization and
biasing scans toward the tail of the old sublist.

Port of Percona Server commit dc344fba7e3d272ac18e8d6589b37c678f1e1ad5
by Paweł Olchawa (PS-11446).
Aleksey Midenkov
MDEV-39074 trans_rollback_stmt(THD *): Assertion `! thd->in_sub_stmt' failed.

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.

Guard it with spcont/in_sub_stmt, deferring cleanup to the
enclosing top-level statement. Other callers run only from the
top-level SQL slave applier thread, so this doesn't change their
behavior.

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() matched only by table name -- could return
    the TABLE instance the enclosing statement was actively writing
    through, not an idle one. Reusing it would require pre-saving its
    state: record[0]/bitmaps/handler/etc. (will deprecate
    ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG)

  - set_stmt_row_injection()/set_time() calls mutated 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 nested mysql_parse() call, 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.
Marko Mäkelä
MDEV-41166 --backup --innodb-log-checkpoint-now may copy too much

xtrabackup_backup_func(): Request for a checkpoint synchronously
so that recv_sys.find_checkpoint() will observe the effect.

(cherry picked from commit b5ab07c314698e5fa15e5010fb7b78b1728306b5)
Kristian Nielsen
test2 possible fix for fedora 44 uninitialized warning

Signed-off-by: Kristian Nielsen <[email protected]>
Oleksandr Byelkin
Try to fix install
Aleksey Midenkov
MDEV-39074 trans_rollback_stmt(THD *): Assertion `! thd->in_sub_stmt' failed.

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.

Guard it with spcont/in_sub_stmt, deferring cleanup to the
enclosing top-level statement. Other callers run only from the
top-level SQL slave applier thread, so this doesn't change their
behavior.

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() matched only by table name -- could return
    the TABLE instance the enclosing statement was actively writing
    through, not an idle one. Reusing it would require pre-saving its
    state: record[0]/bitmaps/handler/etc. (will deprecate
    ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG)

  - set_stmt_row_injection()/set_time() calls mutated 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 nested mysql_parse() call, 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.
Sergei Golubchik
misc fixes

1. set PLUGIN_HEX_VERSION correctly for bundled plugins
2. don't add GenError dependency for external plugins
3. don't change the policy globally
4. only do EXTERNAL_PLUGIN_POST() if EXTERNAL_PLUGIN_PRE() was done
Oleksandr Byelkin
fix windows test
Aleksey Midenkov
MDEV-39074 trans_rollback_stmt(THD *): Assertion `! thd->in_sub_stmt' failed.

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.

Guard it with spcont/in_sub_stmt, deferring cleanup to the
enclosing top-level statement. Other callers run only from the
top-level SQL slave applier thread, so this doesn't change their
behavior.

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() matched only by table name -- could return
    the TABLE instance the enclosing statement was actively writing
    through, not an idle one. Reusing it would require pre-saving its
    state: record[0]/bitmaps/handler/etc. (will deprecate
    ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG)

  - set_stmt_row_injection()/set_time() calls mutated 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 nested mysql_parse() call, 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.
Sergei Golubchik
misc fixes

1. set PLUGIN_HEX_VERSION correctly for bundled plugins
2. don't add GenError dependency for external plugins
3. don't change the policy globally
Yuchen Pei
MDEV-39525 [to-squash] fix longstr supertype check

The string supertype check is too strict: it bails when src (resp.
dst) is length-limited in chars (i.e. CHAR/VARCHAR) and dst (resp.
src) is length-limited in octets (i.e. TEXT/BLOB).

We change this to conversion of src length limit to that of dst, i.e.

- if src limit is declared in octets and dst limit in chars, convert
  src limit to be in chars, and compare
- if src limit is declared in chars and dst limit in octets, convert
  src limit to be in octets, and compare

This restores the JSON_EXTRACT cases in vcol.vcol_sargable
Aleksey Midenkov
MDEV-39074 trans_rollback_stmt(THD *): Assertion `! thd->in_sub_stmt' failed.

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.

Guard it with spcont/in_sub_stmt, deferring cleanup to the
enclosing top-level statement. Other callers run only from the
top-level SQL slave applier thread, so this doesn't change their
behavior.

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() matched only by table name -- could return
    the TABLE instance the enclosing statement was actively writing
    through, not an idle one. Reusing it would require pre-saving its
    state: record[0]/bitmaps/handler/etc. (will deprecate
    ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG)

  - set_stmt_row_injection()/set_time() calls mutated 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 nested mysql_parse() call, 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.
Marko Mäkelä
fixup! 204b63badbe6ef38b3a57a4bd0c429b76ac48c63
Kristian Nielsen
Test possible fix for fedora 44 uninitialized warning

Signed-off-by: Kristian Nielsen <[email protected]>