Console View
|
Categories: connectors experimental galera main |
|
| connectors | experimental | galera | main | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Daniel Black
daniel@mariadb.org |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| deb: add Ubuntu stonking as next release | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Dave Gosselin
dave.gosselin@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-28509: Dereferenced null pointer of type 'struct JOIN_TAB' in add_key_field setup_group no longer writes Item::marker. The ONLY_FULL_GROUP_BY check now tests GROUP BY membership by walking the GROUP BY list. A query that defines a WINDOW but never refers to it could crash in add_key_field, for example WITH cte AS (SELECT i FROM (SELECT i FROM t1 GROUP BY i) dt WINDOW w AS (PARTITION BY i)) SELECT a.i FROM cte a JOIN cte b ON a.i=b.i WHERE a.i != 5; A query that defines a WINDOW goes through setup_group, which set marker to MARKER_UNDEF_POS (-1) on each GROUP BY expression so that the ONLY_FULL_GROUP_BY check could skip it. Other code reads marker as a set of flag bits (-1 sets all bits). Item_direct_view_ref::grouping_field_transformer_for_where then took the ref as flagged for substitution and followed a path that ends in the crash. The ONLY_FULL_GROUP_BY check was the only reader of that value, so MARKER_UNDEF_POS is removed. The necessary check is local to the setup_group function. Co-Authored-By: Claude Opus 5.5 <[email protected]> |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Raghunandan Bhat
raghunandan.bhat96@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-41193: ASAN heap-buffer-overflow in ha_connect::CheckCond after select from Connect table Problem: When CONNECT engine pushes a WHERE clause down to an external table, it writes the filter into the work area, without checking how much space is left. A large string literal in the WHERE clause can overflow the work area allocated by the engine. For ex: if connect_work_size is set to 4MB and the string literal in the WHERE clause is larger than 4MB, it can grow past the allocated work area. Fix: Track the space left in the work area and check it before writing. If the filter doesn't fit, drop it instead of writing past the buffer. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Oleksandr Byelkin
sanja@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-41186 Fix stack-buffer-overflow in make_unique_constraint_name A multi-byte PERIOD name at the 64-character limit (192 bytes) filled the name buffer exactly, leaving no room for the '_N' suffix appended when generating a unique name for the implicit CHECK constraint. Truncate on a character boundary, but only once a suffix is actually needed (mirroring make_unique_key_name), so a non-colliding name is never altered. Co-Authored-By: Claude Sonnet 5 <[email protected]> |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Vladislav Vaintroub
vvaintroub@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-40967 PROXY protocol host check sent in clear text mid-SSL handshake Defer the host-privileged/host-blocked check for a PROXY-header-derived address until after the client's SSL handshake completes, instead of sending it immediately in clear text. Co-Authored-By: Claude Sonnet 5 <[email protected]> |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Sergei Golubchik
serg@mariadb.org |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MDEV-39625 plugin/auth_pam/testing/CMakeLists.txt breaks INSTALL_MYSQLTESTDIR= suppression | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Sergei Golubchik
serg@mariadb.org |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-40229 I_S.VECTOR_INDEXES: Unclear or inconsistent semantics of INDEX_SIZE 11.8 fix for I_S.TABLES.INDEX_LENGTH column Take into account both data_file_length and index_file_length, they both take space and both belong to the vercor index. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Vladislav Vaintroub
vvaintroub@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
fix macOS: -undefined dynamic_lookup instead of linking mariadbd mariadbd is not exported, so it can't be a dependency of mariadb_private (previous commit dropped that link entirely). On Apple platforms specifically, -bundle_loader isn't the only option - a MODULE there is a loadable bundle, same class as Python/Perl/Ruby native extensions, and -Wl,-undefined,dynamic_lookup is the standard way those defer symbol resolution to load time, same as ELF already does for free. CMake's own Platform/Darwin.cmake confirms this isn't the default for MODULE targets, so it needs to be added explicitly. Scoped to APPLE specifically, not "not Linux" - FreeBSD/OpenBSD/NetBSD and Solaris/illumos are ELF like Linux and never needed anything here. Assisted-by: Claude:claude-5-sonnet |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Khaled Riyad
khaled57.dev@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-37563 Crash on a FOR loop with a row comparison in the upper bound The rule for_loop_bound_expr left the items of a FOR loop bound on THD::free_list, so they were cleaned up after the wrong instruction, and a row comparison in the upper bound kept a pointer to memory freed at the end of an instruction. Store the items in the sp_assignment_lex of the bound, like assignment_source_expr does, and pass them back to THD::free_list right before generating the instruction that evaluates that bound. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Rucha Deodhar
rucha.deodhar@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Sergei Golubchik
serg@mariadb.org |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-40608 MariaDB-devel is incomplete for plugins This works on Linux and on Windows, with rpm/deb/tar.gz/zip installations. For rpm/deb it just works, for tar.gz/zip there is no standard location, so one needs to configure plugin with -DCMAKE_PREFIX_PATH=/pah/to/mariadb/basedir after that, `cmake --install .` works too, installing in the same basedir. `cmake --build . --target package` works, creating rpm/deb/targz/zip depending on whether it's Linux or Windows and whether -DRPM or -DDEB was specified. * create and install mariadb-plugin-config.cmake * for now it only supports one plugin per project, error out if there are many * deb: move all headers that plugins need to libmariadb-dev, together with libmysqlservices.a. At least until we'll create mariadb-plugin-dev. Nobody should need huge libmariadbd-dev to develop a plugin * rpm: all in MariaDB-devel already, no changes here * install wsrep headers too, THD layout depends on WITH_WSREP * show DBUG_OFF, ENABLED_DEBUG_SYNC, and SAFE_MUTEX to plugins, same reason (it doesn't happen automatically as they're not in my_config.h) * but don't install config.h - high chance of name conflict with other projects and it's an exact copy of my_config.h anyway. * adjust plugin.cmake to work for external plugins * move server-internal part to top-level CMakeLists.txt * remove double-defined macros from unireg.h (the guard doesn't help if unireg.h is included first) * package plugin metadata as yaml in .tar.gz/.zip ColumnStore, until fixed, needs a backward-compatibility workaround |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Raghunandan Bhat
raghunandan.bhat96@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-41193: ASAN heap-buffer-overflow in ha_connect::CheckCond after select from Connect table Problem: When CONNECT engine pushes a WHERE clause down to an external table, it writes the filter into the work area, without checking how much space is left. A large string literal in the WHERE clause can overflow the work area allocated by the engine. For ex: if connect_work_size is set to 4MB and the string literal in the WHERE clause is larger than 4MB, it can grow past the allocated work area. Fix: Track the space left in the work area and check it before writing. If the filter doesn't fit, drop it instead of writing past the buffer. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Raghunandan Bhat
raghunandan.bhat96@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-41193: ASAN heap-buffer-overflow in ha_connect::CheckCond after select from Connect table Problem: When CONNECT engine pushes a WHERE clause down to an external table, it writes the filter into the work area, without checking how much space is left. A large string literal in the WHERE clause can overflow the work area allocated by the engine. For ex: if connect_work_size is set to 4MB and the string literal in the WHERE clause is larger than 4MB, it can grow past the allocated work area. Fix: Track the space left in the work area and check it before writing. If the filter doesn't fit, drop it instead of writing past the buffer. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Vladislav Vaintroub
vvaintroub@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
fix Windows build: split mariadb_private into headers-only and full parts Split mariadb_private into a headers-only part, linked to static-only plugins, and one that additionally links the server library (to fix unresolved dependencies) for module plugins. Fixes the cycle reported by CMake. Also, rocksdb_aux_lib needs server headers too. Also fixes INSTALL_RUNTIME_DEPS: it walked only one level of LINK_LIBRARIES to find same-build shared libs to exclude, which worked while plugins linked server/mariadbd directly. Now that they link the mariadb_private INTERFACE library instead, server only shows up via its INTERFACE_LINK_LIBRARIES, so the walk is now transitive. Assisted-by: Claude:claude-5-sonnet |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Vladislav Vaintroub
vvaintroub@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-40967 PROXY protocol host check sent in clear text mid-SSL handshake Defer the host-privileged/host-blocked check for a PROXY-header-derived address until after the client's SSL handshake completes, instead of sending it immediately in clear text. Co-Authored-By: Claude Sonnet 5 <[email protected]> |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Raghunandan Bhat
raghunandan.bhat96@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-41193: ASAN heap-buffer-overflow in ha_connect::CheckCond after select from Connect table Problem: When CONNECT engine pushes a WHERE clause down to an external table, it writes the filter into the work area, without checking how much space is left. A large string literal in the WHERE clause can overflow the work area allocated by the engine. For ex: if connect_work_size is set to 4MB and the string literal in the WHERE clause is larger than 4MB, it can grow past the allocated work area. Fix: Track the space left in the work area and check it before writing. If the filter doesn't fit, drop it instead of writing past the buffer. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Rucha Deodhar
rucha.deodhar@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-40127: Server hangs when setting NEW=OLD on a multi-row table Analysis: m_fields was keeping old fields around across executions instead of being cleared, causing stale state and hangs/crashes on re-execution. Fix: Clear m_fields in cleanup(). |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Vladislav Vaintroub
vvaintroub@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
experiment: drop mariadbd link from mariadb_private on non-MSVC/AIX/Linux This was causing "install(EXPORT ...) includes target mariadb_private which requires target mariadbd that is not in any export set" on macOS, since nothing exports mariadbd. Removing it here to see whether anything actually needs it in practice - CMake's default MODULE creation flags on Apple platforms don't add -undefined dynamic_lookup (checked cmake's own Platform/Darwin.cmake), so if a plugin does reference symbols outside mysqlservices, this should fail to link rather than silently misbehave. Assisted-by: Claude:claude-5-sonnet |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Brandon Nesterenko
brandon.nesterenko@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-40906: rpl.rpl_gtid_thread_id assert_grep.inc failed rpl.rpl_gtid_thread_id could fail sporadically due to a non-deterministic slave state during an assert. The test asserted that a certain number of transaction's exist in the slave's binary log file; however, there was no sync between the master and slave after the last transaction executed on the master. This means the slave's binary log could be checked before the transaction ever was sent to/committed on the slave. The fix is to simply sync the master and slave before checking the slave's binary log. Signed-off-by: Brandon Nesterenko <[email protected]> |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Aleksey Midenkov
midenok@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-41050 versioned DELETE via row_end index leaves a row undeleted 1. Aria/MyISAM engines A system-versioned DELETE on a MyISAM or Aria table indexed by row_end could leave the last current row undeleted. The delete scans the current rows via an equality search on row_end = MAX, which is driven by mi_rnext_same/maria_rnext_same. That function keeps the search's reference key in lastkey2 and uses the HA_STATE_RNEXT_SAME flag to remember it has already stored it. This reference-key mechanism is exactly what lets the scan modify rows it is walking without skipping them. Deleting a versioned row is an in-place update of row_end, and the update reuses lastkey2 as scratch space for the changed key, so it clears HA_STATE_RNEXT_SAME to request that rnext_same re-store its reference on the next call. However, TABLE::delete_row wraps the update in HA_EXTRA_REMEMBER_POS/HA_EXTRA_RESTORE_POS, and RESTORE_POS restored the whole saved info->update word, resurrecting the HA_STATE_RNEXT_SAME bit that the update had just cleared. As a result rnext_same skipped rebuilding its reference key and compared subsequent keys against the now-overwritten lastkey2, hitting a spurious end-of-file and terminating the scan one row early, defeating the engine's own protection against a Halloween-style skip. Fixed by preserving the current HA_STATE_RNEXT_SAME bit across RESTORE_POS instead of restoring the stale saved value. See also the HEAP fix below: same root cause, different per-engine mechanism. 2. HEAP engine The row loss also reproduces on the MEMORY (HEAP) engine. A system- versioned DELETE scans the current rows on the row_end index and turns each delete into an in-place update of row_end, so it modifies the very index it is walking. When the changed key is the scanned one (info->lastinx), hp_delete_key() repositions the cursor but heap_update() leaves info->update untouched, so HA_STATE_NEXT_FOUND from the preceding heap_rnext() stays set. The next heap_rnext() then sees current_ptr == 0 with that bit and takes the "!current_ptr && HA_STATE_NEXT_FOUND" guard as a false end-of-file, stopping one row early. Fixed by clearing HA_STATE_NEXT_FOUND when the scanned index key changed. HA_STATE_AKTIV is kept (unlike heap_delete): the row is updated, not removed, so a following op must not fail test_active(). The bit is only set after a heap_rnext(), so a plain single-row UPDATE never reaches this. See also the Aria/MyISAM fix above: same root cause, different per-engine mechanism. 3. Why the fix differs per engine, and InnoDB Aria and HEAP both trace their handler design back to MyISAM, hence the same root cause (stale scan bookkeeping after an in-place key change) in all three, fixed at each engine's own bookkeeping spot. InnoDB needs no fix: its persistent cursor survives concurrent index modification by design, already covered by this same test under the timestamp combination (default-storage-engine=innodb), which passes unmodified. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Rucha Deodhar
rucha.deodhar@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-40124: Assertion `m_sp == m_thd->spcont->m_sp' failed virtual Item *Item_splocal::this_item(), UBSAN : member call on null pointer of type 'Sp_rcontext_handler' in sql/item.cc Analysis: Comparing trigger row objects (IF NEW = OLD) calls cmp_row_type(), which uses element_index(i). Item_trigger_row was missing its own element_index() override, falling back to Item_splocal's version and crashing/asserting on local context lookup. Fix: Override element_index(i) in Item_trigger_row to return m_fields.elem(i) directly. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Vladislav Vaintroub
vvaintroub@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-32745 followup - fix Windows build on a machine without Developer Mode. In custom CMake target, do not attempt symlink with hardlink fallback, this makes VS build fail since stderr is written (and attempts to suppress stderr fail in Ninja generator) Just use hardlinks, no substantial difference in the given scenario. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Rucha Deodhar
rucha.deodhar@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Analysis: Buffer overflow crashes and empty key duplication in check_key_in_list. Fix: Checking result buffer validity prevents segmentation faults on empty strings and malformed inputs while preserving correct key matching behavior. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Rucha Deodhar
rucha.deodhar@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-40130: SIGSEGV in Field_iterator_table::set_table at sql/table.h:3350 Analysis: The if...else block in sp_head::add_instr_core() checks for existence of items in m_cur_instr_trig_row_items. Since we also have elements in m_cur_instr_trig_field_items, the block where we check for m_cur_instr_trig_row_items and populate m_trg_table_row gets skipped entirely. Additionally the missing iterate_trigger_row_and_run_func over m_trg_table_row does not setup table and trigger values either. This eventually causes crash while setting table for field iterator during trigger execution. This patch also fixes the list-transfer logic in sp_head::add_instr_core() to prevent row items from being dropped and adds a corresponding create-time setup_field() loop in Table_triggers_list::create_trigger() to ensure trigger row items are properly initialized. Fix: Check for m_cur_instr_trig_row_items exclusively. Also added iterate_trigger_row_and_run_func over m_trg_table_row to set up the table and over relevant values correctly. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Rex Johnston
rex.johnston@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-41258 Crash on EXECUTE after FLUSH TABLES with DEFAULT NEXTVAL() A prepared statement adds a TABLE_LIST for each sequence used in a column DEFAULT and keeps it for later executions. That TABLE_LIST borrowed its db/table name pointers from the owning TABLE, and was found again by comparing those pointers. After FLUSH TABLES the TABLE is freed, so the next EXECUTE reads freed names (assertion in get_table_def_key()) and writes through a stale linked_table. Fix: - copy the names onto the statement arena; - record the owning TABLE_LIST and match an owner's entries by position instead of by name pointer; - relink existing entries on every execution; - keep the DEFAULT used in the statement text: set_parameters() overwrote lex->default_used with "a parameter is DEFAULT", so UPDATE ... SET a = DEFAULT did not relink on EXECUTE. This commit was prepared with Claude Code (Opus 5.5), which analysed the rr recording to find the use-after-free, wrote the fix and the test. Tidied up by Rex. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Vladislav Vaintroub
vvaintroub@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-40961 fix SBOM generation - randomize UUID Previously UUID appeared to be constant. Add randomizer to it. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Raghunandan Bhat
raghunandan.bhat96@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-41193: ASAN heap-buffer-overflow in ha_connect::CheckCond after select from Connect table Problem: When CONNECT engine pushes a WHERE clause down to an external table, it writes the filter into the work area, without checking how much space is left. A large string literal in the WHERE clause can overflow the work area allocated by the engine. For ex: if connect_work_size is set to 4MB and the string literal in the WHERE clause is larger than 4MB, it can grow past the allocated work area. Fix: Track the space left in the work area and check it before writing. If the filter doesn't fit, drop it instead of writing past the buffer. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Sergei Golubchik
serg@mariadb.org |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mariadb_private | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Aleksey Midenkov
midenok@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-41050 versioned DELETE via row_end index leaves a row undeleted 1. Aria/MyISAM engines A system-versioned DELETE on a MyISAM or Aria table indexed by row_end could leave the last current row undeleted. The delete scans the current rows via an equality search on row_end = MAX, which is driven by mi_rnext_same/maria_rnext_same. That function keeps the search's reference key in lastkey2 and uses the HA_STATE_RNEXT_SAME flag to remember it has already stored it. This reference-key mechanism is exactly what lets the scan modify rows it is walking without skipping them. Deleting a versioned row is an in-place update of row_end, and the update reuses lastkey2 as scratch space for the changed key, so it clears HA_STATE_RNEXT_SAME to request that rnext_same re-store its reference on the next call. However, TABLE::delete_row wraps the update in HA_EXTRA_REMEMBER_POS/HA_EXTRA_RESTORE_POS, and RESTORE_POS restored the whole saved info->update word, resurrecting the HA_STATE_RNEXT_SAME bit that the update had just cleared. As a result rnext_same skipped rebuilding its reference key and compared subsequent keys against the now-overwritten lastkey2, hitting a spurious end-of-file and terminating the scan one row early, defeating the engine's own protection against a Halloween-style skip. Fixed by preserving the current HA_STATE_RNEXT_SAME bit across RESTORE_POS instead of restoring the stale saved value. See also the HEAP fix below: same root cause, different per-engine mechanism. 2. HEAP engine The row loss also reproduces on the MEMORY (HEAP) engine. A system- versioned DELETE scans the current rows on the row_end index and turns each delete into an in-place update of row_end, so it modifies the very index it is walking. When the changed key is the scanned one (info->lastinx), hp_delete_key() repositions the cursor but heap_update() leaves info->update untouched, so HA_STATE_NEXT_FOUND from the preceding heap_rnext() stays set. The next heap_rnext() then sees current_ptr == 0 with that bit and takes the "!current_ptr && HA_STATE_NEXT_FOUND" guard as a false end-of-file, stopping one row early. Fixed by clearing HA_STATE_NEXT_FOUND when the scanned index key changed. HA_STATE_AKTIV is kept (unlike heap_delete): the row is updated, not removed, so a following op must not fail test_active(). The bit is only set after a heap_rnext(), so a plain single-row UPDATE never reaches this. See also the Aria/MyISAM fix above: same root cause, different per-engine mechanism. 3. Why the fix differs per engine, and InnoDB Aria and HEAP both trace their handler design back to MyISAM, hence the same root cause (stale scan bookkeeping after an in-place key change) in all three, fixed at each engine's own bookkeeping spot. InnoDB needs no fix: its persistent cursor survives concurrent index modification by design, already covered by this same test under the timestamp combination (default-storage-engine=innodb), which passes unmodified. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Dave Gosselin
dave.gosselin@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-28509: Dereferenced null pointer of type 'struct JOIN_TAB' in add_key_field This patch fixes a crash when calculating join statistics during query optimization for queries with an unused WINDOW definition. Put another way, the system may crash when a query defines a WINDOW but doesn't then refer to it. Item::marker is overloaded for different uses, many of which treat it as a bit field. However, the setup_group function used it to mark that a field was found when traversing a GROUP BY. Originally, this marking set the Item::marker field to 1 to indicate that it was found. Later on in setup_group (and only when SQL mode ONLY_FULL_GROUP_BY is enabled), we would skip any such marked fields when checking that fields only referenced those found in the GROUP BY; otherwise, it would be silly to find fields of the GROUP BY within the GROUP BY field itself. Setting Item::marker to 1 seemed mostly harmless at that point in time. But later, in git sha 4d143a6ff6, we introduced several changes: (1) the value of marker in setup_group was changed from 1 to UNDEF_POS, (2) Item::marker was changed from uint8 to int8, and (3) UNDEF_POS which is defined to be -1 was also added. Queries that define WINDOWs internally will setup groups and orders as part of query processing via the setup_group function. Consequently because of the behavior described earlier above, such queries may have items with markers as MARKER_UNDEF_POS (-1) which is the same as marking all of the flag bits as set. This is disastrous for those users of Item::marker which refer to it as a bit field, because every flag bit appears set at once, including bits that are mutually exclusive in meaning. Even a masked test such as marker & MARKER_SUBSTITUTION returns true when marker is -1. In particular, the method Item_direct_view_ref::grouping_field_transformer_for_where then treats the ref as flagged for substitution and takes the wrong execution path, leading to the crash. The only reader of that marker was the GROUP BY membership test in setup_group itself, so setup_group no longer writes marker. A helper, item_in_group_list, yields the same info by walking the GROUP BY list instead. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Thirunarayanan Balathandayuthapani
thiru@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-19574: innodb_stats_method is not honored when innodb_stats_persistent=ON Problem: ======= When persistent statistics are enabled (innodb_stats_persistent=ON), the innodb_stats_method setting is not properly utilized during statistics calculation. The statistics collection functions always use a hardcoded default behavior for NULL value comparison instead of respecting the configured stats method. This affects the accuracy of n_diff_key_vals (distinct key count), particularly for indexes with nullable columns containing NULL values. Moreover, stat_n_non_null_key_vals[] was never computed for persistent statistics; it stayed at the 0 that dict_stats_empty_index() assigns. With innodb_stats_method=nulls_ignored, innodb_rec_per_key() therefore always found n_diff <= n_null and reported one record per key for every index. This impacts the query optimizer, which makes decisions based on inaccurate cardinality estimates. Solution: ======== Introduced IndexLevelStats to collect statistics at a specific B-tree level during index analysis. Introduced PageStats to collect statistics for leaf page analysis. Refactored the following functions: dict_stats_analyze_index_level() to IndexLevelStats::analyze_level() dict_stats_analyze_index_for_n_prefix() to IndexLevelStats::sample_leaf_pages() dict_stats_analyze_index_below_cur() to PageStats::scan_below() dict_stats_scan_page() to PageStats::scan() The innodb_stats_method value is read once per table in dict_stats_update_persistent() and passed down, so that all indexes of a table are analyzed with the same method. Add the stats method name to stat_description when innodb_stats_method has a non-default value. The suffix is dropped when the description is already full. Added the new stat name n_nonnull_fld01, n_nonnull_fld02, etc. with a stats description, to indicate how many non-null values exist for the nth field of the index. This value is retrieved and stored in the index statistics in dict_stats_fetch_index_stats_step(). The counts are per column, not per n-column prefix. rec_get_n_blob_pages(): Calculate the number of externally stored pages for a record, using ceiling division by the usable BLOB page payload (blob_part_size), which differs between ROW_FORMAT=COMPRESSED (zip_size minus FIL_PAGE_DATA) and the other formats (srv_page_size minus the BLOB header and the page trailer). For ROW_FORMAT=COMPRESSED the length in the field reference is the uncompressed length, so the result is an upper bound. When the leaf level is scanned in full, the number of leaf pages that were scanned is reported as n_leaf_pages for a multi level index. Before, result.n_leaf_pages was overwritten with index->stat_n_leaf_pages, which dict_stats_empty_index() had just set to 1, so every index that took the full scan path reported n_leaf_pages=1. Single page indexes report 1. This changes cardinality estimates and therefore leads to multiple changes in existing test cases. Non-null values are counted only at the leaf level, since only leaf pages hold actual records. A full scan of the leaf level counts them exactly. When the level is sampled, the per column count is derived from the sampled leaves with the same formula as n_diff: n_ordinary_leaf_pages * n_non_null_all_analyzed_pages / n_leaf_pages_to_analyze This is an estimate for NOT NULL columns as well: the sampled leaves may hold fewer or more records than the average, and a dive that stops at a boring page contributes nothing to the sum while still counting in the divisor. innodb_rec_per_key(): stat_n_non_null_key_vals[i] holds the number of records in which the i-th indexed column alone is not NULL, while what has to be excluded here is the number of records whose first i+1 columns are all not NULL, because that is the population which the n-column prefix statistic stat_n_diff_key_vals[i] has to be corrected against when innodb_stats_method=nulls_ignored: with NULLs compared as unequal, every record carrying a NULL anywhere in the prefix adds a distinct value of its own to n_diff. PageStats::scan(): n_non_null is accumulated and assigned only for leaf pages, so that a non-leaf scan cannot leave a node pointer count behind when scan_below() stops at a boring page without reaching a leaf. IndexLevelStats::reset_for_level() also clears n_diff[], and dict_stats_analyze_index() zero initializes the buffer backing it, so that a level scan which finds no records (a failed btr_pcur_open_level(), or a non-leaf page whose first record is not marked as the leftmost one on the level) leaves n_diff[] at 0 instead of stale values. IndexLevelStats::sample_leaf_pages() returns early when the group boundaries for the prefix are empty, which is the same condition. IndexLevelStats::analyze_level(): Instead of copying the last record of the page, retain the latch on the page until the record has been compared with the first record of the next page dict_stats_fetch_index_stats_step() no longer resets stat_n_non_null_key_vals[] while processing an n_diff_pfxNN row: dict_stats_empty_table() has already cleared the array before the fetch, and with n_nonnull_fldNN rows now being read too, that reset would make the result depend on the order in which the rows arrive. dict_stats_save(): now static function in dict0stats.cc that takes the innodb_stats_method value, and is removed from dict0stats.h. dict_stats_update_persistent() saves the statistics itself, so its callers no longer have to. Replaced btr_rec_get_externally_stored_len() with rec_get_n_blob_pages() in dict0stats.cc. btr_rec_get_field_ref_offs() and btr_rec_get_field_ref(), together with the BTR_BLOB_HDR_* macros, were moved from btr0cur.cc to btr0cur.h so that rec_get_n_blob_pages() can reuse them; btr_rec_get_field_ref_offs() is now a noexcept function returning size_t. Changed stat_n_diff_key_vals and stat_n_non_null_key_vals from ib_uint64_t* to uint64_t* len_is_stored(): simplified to a single comparison, which is equivalent for the unsigned lengths that it is used with. Removed the unused UNIV_STATS_DEBUG build macro (univ.i) and turned the DEBUG_PRINTF() helper in dict0stats.cc into an unconditional no-op |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Alessandro Vetere
iminelink@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-40408 btr_page_reorganize_low() uses the buffer pool just to obtain a scratch block Add buf_pool.scratch_buf, a pool of page frames that the page reorganization operations use instead of taking a block from the global buffer pool. buf_pool_t::scratch_buffer: A singly linked list of chunks of buf_tmp_buffer_t slots. A thread holds at most one slot at a time, and it holds a page latch while doing so, which is why reserve() must not wait for the buffer pool or for I/O. When every slot is in use, reserve() appends a chunk that holds twice the slots of the last one, up to a limit on the size of one chunk. Only grow() waits, on the mutex that serializes it and on the allocator. A chunk is never moved or freed before close(), so a thread can keep using the slot that it reserved while another thread appends a chunk. Debug builds start with a single slot, so that a second concurrent page reorganization exercises grow(). buf_pool_t::scratch_buffer::shrink(): Free the page frames of the slots that are not in use. The slots and the chunks are kept, because a slot costs 32 bytes while a page frame costs srv_page_size. A frame survives the first pass, because that pass only clears the used flag. The master thread calls this often while the server is idle and rarely while it is active, and buf_pool_t::garbage_collect() calls it under memory pressure, where it ignores the used flag, because releasing these frames is much cheaper than shrinking the buffer pool. buf_pool_t::io_buf_t::acquire(): Factor out the scan for an unreserved slot, which io_buf_t::reserve() ran twice and the scratch buffer reuses. btr_page_reorganize_low(), page_zip_reorganize(): Obtain the scratch page frame from buf_pool.scratch_buf. This removes the buf_pool.mutex acquisition and the free block wait that buf_block_alloc() could perform while at least a page X-latch was being held. btr_page_reorganize_low() also used to leak the block on its error paths; a single exit now releases the slot. page_copy_rec_list_end_no_locks(), lock_move_reorganize_page(): Take the source page frame instead of a source buf_block_t, because the source is no longer a buffer pool block. In page_copy_rec_list_end_no_locks() the source page is page_align(rec) at every call site, so only the record is passed. buf_tmp_buffer_t::acquire(), buf_tmp_buffer_t::release(): Use acquire and release memory ordering, so that the page frame pointer of a slot is published to the next thread that reserves the slot. acquire() also reads the flag before the exchange, so that a scan across an array of slots does not write to the slots that it finds reserved. release() also marks the page frame undefined for Valgrind and MSan, because the frame stays allocated for the next reserver and no deallocation marks it. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Dave Gosselin
dave.gosselin@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-28509: Dereferenced null pointer of type 'struct JOIN_TAB' in add_key_field setup_group no longer writes Item::marker. The ONLY_FULL_GROUP_BY check now tests GROUP BY membership by walking the GROUP BY list. A query that defines a WINDOW but never refers to it could crash in add_key_field, for example WITH cte AS (SELECT i FROM (SELECT i FROM t1 GROUP BY i) dt WINDOW w AS (PARTITION BY i)) SELECT a.i FROM cte a JOIN cte b ON a.i=b.i WHERE a.i != 5; A query that defines a WINDOW goes through setup_group, which set marker to MARKER_UNDEF_POS (-1) on each GROUP BY expression so that the ONLY_FULL_GROUP_BY check could skip it. Other code reads marker as a set of flag bits (-1 sets all bits). Item_direct_view_ref::grouping_field_transformer_for_where then took the ref as flagged for substitution and followed a path that ends in the crash. The ONLY_FULL_GROUP_BY check was the only reader of that value, so MARKER_UNDEF_POS is removed. The necessary check is local to the setup_group function. Co-Authored-By: Claude Opus 5.5 <[email protected]> |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Vladislav Vaintroub
vvaintroub@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-40608 build mysqlservices without an embedded CRT requirement mysqlservices only exposes a thin C API, no CRT state crosses it, so don't force whatever CRT/config built the server onto a plugin linking it. Without /Zl, a plugin built in a config with no matching installed mysqlservices variant (CMake silently substitutes one - verified with a toy project) gets an ignorable but noisy LNK4098 warning. Assisted-by: Claude:claude-5-sonnet |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Rucha Deodhar
rucha.deodhar@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-39049: Memory corruption & crash in check_key_in_list upon using JSON_KEYS after modifying character set name/collation Analysis: Buffer overflow crashes and empty key duplication in check_key_in_list. Fix: Checking result buffer validity prevents segmentation faults on empty strings and malformed inputs while preserving correct key matching behavior. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Rex Johnston
rex.johnston@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-41258 Crash on EXECUTE after FLUSH TABLES with DEFAULT NEXTVAL() A prepared statement adds a TABLE_LIST for each sequence used in a column DEFAULT and keeps it for later executions. That TABLE_LIST borrowed its db/table name pointers from the owning TABLE, and was found again by comparing those pointers. After FLUSH TABLES the TABLE is freed, so the next EXECUTE reads freed names (assertion in get_table_def_key()) and writes through a stale linked_table. Fix: - copy the names onto the statement arena; - record the owning TABLE_LIST and match an owner's entries by position instead of by name pointer; - relink existing entries on every execution; - keep the DEFAULT used in the statement text: set_parameters() overwrote lex->default_used with "a parameter is DEFAULT", so UPDATE ... SET a = DEFAULT did not relink on EXECUTE. This commit was prepared with Claude Code (Opus 5.5), which analysed the rr recording to find the use-after-free, wrote the fix and the test, and ran the main, sql_sequence, compat/oracle, versioning, period, vcol, gcol and json suites in normal and --ps-protocol mode. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Thirunarayanan Balathandayuthapani
thiru@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-19574: innodb_stats_method is not honored when innodb_stats_persistent=ON Problem: ======= When persistent statistics are enabled (innodb_stats_persistent=ON), the innodb_stats_method setting is not properly utilized during statistics calculation. The statistics collection functions always use a hardcoded default behavior for NULL value comparison instead of respecting the configured stats method. This affects the accuracy of n_diff_key_vals (distinct key count), particularly for indexes with nullable columns containing NULL values. Moreover, stat_n_non_null_key_vals[] was never computed for persistent statistics; it stayed at the 0 that dict_stats_empty_index() assigns. With innodb_stats_method=nulls_ignored, innodb_rec_per_key() therefore always found n_diff <= n_null and reported one record per key for every index. This impacts the query optimizer, which makes decisions based on inaccurate cardinality estimates. Solution: ======== Introduced IndexLevelStats to collect statistics at a specific B-tree level during index analysis. Introduced PageStats to collect statistics for leaf page analysis. Refactored the following functions: dict_stats_analyze_index_level() to IndexLevelStats::analyze_level() dict_stats_analyze_index_for_n_prefix() to IndexLevelStats::sample_leaf_pages() dict_stats_analyze_index_below_cur() to PageStats::scan_below() dict_stats_scan_page() to PageStats::scan() The innodb_stats_method value is read once per table in dict_stats_update_persistent() and passed down, so that all indexes of a table are analyzed with the same method. Add the stats method name to stat_description when innodb_stats_method has a non-default value. The suffix is dropped when the description is already full. Added the new stat name n_nonnull_fld01, n_nonnull_fld02, etc. with a stats description, to indicate how many non-null values exist for the nth field of the index. This value is retrieved and stored in the index statistics in dict_stats_fetch_index_stats_step(). The counts are per column, not per n-column prefix. rec_get_n_blob_pages(): Calculate the number of externally stored pages for a record, using ceiling division by the usable BLOB page payload (blob_part_size), which differs between ROW_FORMAT=COMPRESSED (zip_size minus FIL_PAGE_DATA) and the other formats (srv_page_size minus the BLOB header and the page trailer). For ROW_FORMAT=COMPRESSED the length in the field reference is the uncompressed length, so the result is an upper bound. When the leaf level is scanned in full, the number of leaf pages that were scanned is reported as n_leaf_pages for a multi level index. Before, result.n_leaf_pages was overwritten with index->stat_n_leaf_pages, which dict_stats_empty_index() had just set to 1, so every index that took the full scan path reported n_leaf_pages=1. Single page indexes report 1. This changes cardinality estimates and therefore leads to multiple changes in existing test cases. Non-null values are counted only at the leaf level, since only leaf pages hold actual records. A full scan of the leaf level counts them exactly. When the level is sampled, the per column count is derived from the sampled leaves with the same formula as n_diff: n_ordinary_leaf_pages * n_non_null_all_analyzed_pages / n_leaf_pages_to_analyze This is an estimate for NOT NULL columns as well: the sampled leaves may hold fewer or more records than the average, and a dive that stops at a boring page contributes nothing to the sum while still counting in the divisor. innodb_rec_per_key(): stat_n_non_null_key_vals[i] holds the number of records in which the i-th indexed column alone is not NULL, while what has to be excluded here is the number of records whose first i+1 columns are all not NULL, because that is the population which the n-column prefix statistic stat_n_diff_key_vals[i] has to be corrected against when innodb_stats_method=nulls_ignored: with NULLs compared as unequal, every record carrying a NULL anywhere in the prefix adds a distinct value of its own to n_diff. PageStats::scan(): n_non_null is accumulated and assigned only for leaf pages, so that a non-leaf scan cannot leave a node pointer count behind when scan_below() stops at a boring page without reaching a leaf. IndexLevelStats::reset_for_level() also clears n_diff[], and dict_stats_analyze_index() zero initializes the buffer backing it, so that a level scan which finds no records (a failed btr_pcur_open_level(), or a non-leaf page whose first record is not marked as the leftmost one on the level) leaves n_diff[] at 0 instead of stale values. IndexLevelStats::sample_leaf_pages() returns early when the group boundaries for the prefix are empty, which is the same condition. IndexLevelStats::analyze_level(): Instead of copying the last record of the page, retain the latch on the page until the record has been compared with the first record of the next page dict_stats_fetch_index_stats_step() no longer resets stat_n_non_null_key_vals[] while processing an n_diff_pfxNN row: dict_stats_empty_table() has already cleared the array before the fetch, and with n_nonnull_fldNN rows now being read too, that reset would make the result depend on the order in which the rows arrive. dict_stats_save(): now static function in dict0stats.cc that takes the innodb_stats_method value, and is removed from dict0stats.h. dict_stats_update_persistent() saves the statistics itself, so its callers no longer have to. Replaced btr_rec_get_externally_stored_len() with rec_get_n_blob_pages() in dict0stats.cc. btr_rec_get_field_ref_offs() and btr_rec_get_field_ref(), together with the BTR_BLOB_HDR_* macros, were moved from btr0cur.cc to btr0cur.h so that rec_get_n_blob_pages() can reuse them; btr_rec_get_field_ref_offs() is now a noexcept function returning size_t. Changed stat_n_diff_key_vals and stat_n_non_null_key_vals from ib_uint64_t* to uint64_t* len_is_stored(): simplified to a single comparison, which is equivalent for the unsigned lengths that it is used with. Removed the unused UNIV_STATS_DEBUG build macro (univ.i) and turned the DEBUG_PRINTF() helper in dict0stats.cc into an unconditional no-op |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Sergei Golubchik
serg@mariadb.org |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MDEV-39624 storage/connect/CMakeLists.txt: unguarded generator expression variable causes fatal error at cmake generate phase | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Rucha Deodhar
rucha.deodhar@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-34723: NEW and OLD in a trigger as row variables Implementation: NEW and OLD represent the entire table row. So it can be thought of as list of Item_trigger_field. When we are in a trigger and NEW or OLD is encountered, create Item_trigger_row object with same constructor as Item_trigger_field, it will also be used later while creating Item_trigger_field objects. Populate the m_fields list while fixing fields. Create a corresponding instruction sp_instr_set_trigger_row which will be used to set the values |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Aleksey Midenkov
midenok@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-41050 versioned DELETE via row_end index leaves a row undeleted 1. Aria/MyISAM engines A system-versioned DELETE on a MyISAM or Aria table indexed by row_end could leave the last current row undeleted. The delete scans the current rows via an equality search on row_end = MAX, which is driven by mi_rnext_same/maria_rnext_same. That function keeps the search's reference key in lastkey2 and uses the HA_STATE_RNEXT_SAME flag to remember it has already stored it. This reference-key mechanism is exactly what lets the scan modify rows it is walking without skipping them. Deleting a versioned row is an in-place update of row_end, and the update reuses lastkey2 as scratch space for the changed key, so it clears HA_STATE_RNEXT_SAME to request that rnext_same re-store its reference on the next call. However, TABLE::delete_row wraps the update in HA_EXTRA_REMEMBER_POS/HA_EXTRA_RESTORE_POS, and RESTORE_POS restored the whole saved info->update word, resurrecting the HA_STATE_RNEXT_SAME bit that the update had just cleared. As a result rnext_same skipped rebuilding its reference key and compared subsequent keys against the now-overwritten lastkey2, hitting a spurious end-of-file and terminating the scan one row early, defeating the engine's own protection against a Halloween-style skip. Fixed by preserving the current HA_STATE_RNEXT_SAME bit across RESTORE_POS instead of restoring the stale saved value. See also the HEAP fix below: same root cause, different per-engine mechanism. 2. HEAP engine The row loss also reproduces on the MEMORY (HEAP) engine. A system- versioned DELETE scans the current rows on the row_end index and turns each delete into an in-place update of row_end, so it modifies the very index it is walking. When the changed key is the scanned one (info->lastinx), hp_delete_key() repositions the cursor but heap_update() leaves info->update untouched, so HA_STATE_NEXT_FOUND from the preceding heap_rnext() stays set. The next heap_rnext() then sees current_ptr == 0 with that bit and takes the "!current_ptr && HA_STATE_NEXT_FOUND" guard as a false end-of-file, stopping one row early. Fixed by clearing HA_STATE_NEXT_FOUND when the scanned index key changed. HA_STATE_AKTIV is kept (unlike heap_delete): the row is updated, not removed, so a following op must not fail test_active(). The bit is only set after a heap_rnext(), so a plain single-row UPDATE never reaches this. See also the Aria/MyISAM fix above: same root cause, different per-engine mechanism. 3. Why the fix differs per engine, and InnoDB Aria and HEAP both trace their handler design back to MyISAM, hence the same root cause (stale scan bookkeeping after an in-place key change) in all three, fixed at each engine's own bookkeeping spot. InnoDB needs no fix: its persistent cursor survives concurrent index modification by design, already covered by this same test under the timestamp combination (default-storage-engine=innodb), which passes unmodified. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||