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]> |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Marko Mäkelä
marko.makela@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fixup! 02a56c4d7d22678ba826c5c3a5e4849b05a7ae4f | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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]> |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Aleksey Midenkov
midenok@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-41157 CREATE DATABASE COMMENT overflows db.opt comment buffer Bug 1: put_dbopt() used strmov() to copy schema_comment into a fixed DATABASE_COMMENT_MAXLEN+1 buffer. validate_comment_length() only truncates comment->length in non-strict sql_mode, leaving comment->str NUL-terminated at its original (unbounded) length. strmov() copies until the source NUL, ignoring the truncated length, overflowing the destination buffer for long comments. The fix uses strmake() bounded by comment->length instead, matching the LEX_CSTRING contract (length is authoritative, str need not be NUL-terminated at length). Bug 2: write_db_opt() used strxnmov() to copy the full un-truncated comment until it ran out of buffer space mid-string with no trailing newline. Which made load_db_opt() silently discard the whole unterminated "comment=" line on the next restart, losing the comment entirely instead of just truncating it. The fix bounds the comment copy into db.opt by the already-validated comment->length via strmake(), instead of relying on the source string's own NUL terminator, matching the put_dbopt() fix. Bug 3: validate_comment_length() only runs on a COMMENT clause given in the current statement. ALTER DATABASE without one instead pulls the existing comment off disk via load_db_opt(), which never bounded it. That unvalidated length then reached write_db_opt()'s own comment= copy into its stack buffer, so a legacy or hand-edited db.opt with an overlong comment= line overflowed it on ALTER DATABASE. The fix: load_db_opt() now clamps the parsed comment to DATABASE_COMMENT_MAXLEN right when it reads the "comment=" line, so every consumer (put_dbopt(), write_db_opt()'s ALTER path) always sees an already-bounded value. The clamp itself must truncate by bytes, not characters: Well_formed_prefix()'s LEX_CSTRING overload takes a character count, but DATABASE_COMMENT_MAXLEN sizes the buffers in bytes. Bug 4: validate_comment_length() had the same character/byte confusion, on the primary CREATE/ALTER DATABASE COMMENT path: it passed max_len as a character count to Well_formed_prefix(), so a multi-byte comment could be truncated to max_len characters instead of bytes. Fixed like load_db_opt(): clamp to max_len bytes first, then find the well-formed prefix. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Sergei Petrunia
sergey@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Add comment about Create_tmp_table::m_group | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Georg Richter
georg@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
CONC-854: Fix regression when disable-ssl-verify-server-cert is used with ssl_ca The changes in commit e8c0a16d caused disable-ssl-verify-server-cert (tls_allow_invalid_server_cert) to be ignored whenever ssl_ca, ssl_capath, ssl_crl, or ssl_crlpath were set. While intended as a security measure, this introduced a breaking change for IP-addressed connections (such as 127.0.0.1 and ::1) and environments where clients rely on ssl_ca for CA chain validation while explicitly disabling hostname/IP verification (including MTR test suites). This patch partially reverts e8c0a16d in ma_tls.c: - Restores explicit precedence to tls_allow_invalid_server_cert so disabling server verification bypasses peer verification regardless of whether CA/CRL options are present. - Clearing mysql->net.tls_verify_status when verification is explicitly disabled ensures that cleartext auth-switch guards in my_auth.c continue to function correctly for authorized unverified connections. - Updates unit tests in tls.c.in to reflect the restored behavior for MARIADB_TLS_DISABLE_PEER_VERIFICATION when ssl_ca is configured. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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]> |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Yuchen Pei
ycp@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-39525 Add type checks to vcol index substitution in WHERE When doing a vcol index substitution in a WHERE condition, when the vcol field is not a supertype of the vcol expression, data truncation may result in wrong results after substitution. To fix that, we add a supertype check to WHERE substitution, just like we do for GROUP BY/ORDER BY substitution. However, this fix can be too strict in some cases. For example, if the condition is WHERE <vcol_expr> > 21 where <vcol_expr> has type BIGINT and <vcol_field> has type TINYINT, the supertype check would fail, but here, data truncation would not result in wrong result, because the truncation clamps at extreme values which are -128 or 127 for TINYINT. The key is that the constant compared to is strictly inside the domain of vcol_field. To that end, we add a fallback check for WHERE substitution, that compares vcol_field type domain with the constants. A hole remains for strings with PAD SPACE collations: any const string for comparison could be a result of truncation, even if much shorter than the capacity of the vcol field. For example, for a table with (a varchar(20), vc varchar(5) as (concat('x',a)), index(vc)) vc = 'xabc' evals to 1 for a row with a = 'abc z' but concat('x',a) = 'xabc' evals to 0. The right hand side is 'xabc' could match a vc of varchar(N) for any N >= 4 because there could be arbitrary number of spaces before the truncation. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
bsrikanth-mariadb
srikanth.bondalapati@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-40837: Crash while recording context when character_set_results is NULL Unlike character_set_client or collation_connection, character_set_results can be set to NULL. When recording context for a query in opt_context_store_replay.cc, character_set_results->cs_name was accessed without first checking whether character_set_results itself was NULL, causing a crash. Fix Optimizer_context_recorder::dump_sql_script() to check character_set_results for NULL before accessing cs_name. Added a test for the same, in opt_context_store_stats.test |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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]> |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Yuchen Pei
ycp@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-39525 Add type checks to vcol index substitution in WHERE When doing a vcol index substitution in a WHERE condition, when the vcol field is not a supertype of the vcol expression, data truncation may result in wrong results after substitution. To fix that, we add a supertype check to WHERE substitution, just like we do for GROUP BY/ORDER BY substitution. However, this fix can be too strict in some cases. For example, if the condition is WHERE <vcol_expr> > 21 where <vcol_expr> has type BIGINT and <vcol_field> has type TINYINT, the supertype check would fail, but here, data truncation would not result in wrong result, because the truncation clamps at extreme values which are -128 or 127 for TINYINT. The key is that the constant compared to is strictly inside the domain of vcol_field. To that end, we add a fallback check for WHERE substitution, that compares vcol_field type domain with the constants. A hole remains for strings with PAD SPACE collations: any const string for comparison could be a result of truncation, even if much shorter than the capacity of the vcol field. For example, for a table with (a varchar(20), vc varchar(5) as (concat('x',a)), index(vc)) vc = 'xabc' evals to 1 for a row with a = 'abc z' but concat('x',a) = 'xabc' evals to 0. The right hand side is 'xabc' could match a vc of varchar(N) for any N >= 4 because there could be arbitrary number of spaces before the truncation. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Oleksandr Byelkin
sanja@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-41175 Fix stack-buffer-overflow in backup_log_ddl() backup_log_ddl() sized its stack log buffer assuming each identifier is at most ~40 bytes, but add_name_to_buffer() can expand each identifier character to 5 bytes when re-encoding it into my_charset_filename, so a RENAME TABLE with long, special- character names overflowed the buffer (reported under ASAN). Fixed by sizing the buffer to the true worst case per identifier, and by making add_str_to_buffer() and its callers assert on an explicit end-of-buffer pointer before writing. Co-Authored-By: Claude Sonnet 5 <[email protected]> |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Vladislav Vaintroub
vvaintroub@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-40961 fix SBOM generation - randomize UUID Previously UUID appeared to be constant. Add randomizer to it. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Oleksandr Byelkin
sanja@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-41175 Fix stack-buffer-overflow in backup_log_ddl() backup_log_ddl() sized its stack log buffer assuming each identifier is at most ~40 bytes, but add_name_to_buffer() can expand each identifier character to 5 bytes when re-encoding it into my_charset_filename, so a RENAME TABLE with long, special- character names overflowed the buffer (reported under ASAN). Fixed by sizing the buffer to the true worst case per identifier, and by making add_str_to_buffer() and its callers assert on an explicit end-of-buffer pointer before writing. Co-Authored-By: Claude Sonnet 5 <[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. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Georg Richter
georg@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
CONC-853: Fix TLS verification error mapping parity between OpenSSL and GnuTLS Align certificate verification behavior and status bitmask propagation across OpenSSL and GnuTLS backends. Previously, verification discrepancies led to false negatives, silent auth fallbacks, and assertion failures in `my_auth.c` due to inconsistent mapping of self-signed vs. untrusted leaf certificates and improper bitmask evaluation. Partial rewrite of tls_server.py * Replace deprecated `pyOpenSSL` dependency with `cryptography` library for dynamic SSL context and test certificate generation in tls_server.py. * Ensure full parity and coverage for `test_tls_verify_unknown` across both OpenSSL and GnuTLS backend builds. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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]> |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Yuchen Pei
ycp@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-39525 Add type checks to vcol index substitution in WHERE When doing a vcol index substitution in a WHERE condition, when the vcol field is not a supertype of the vcol expression, data truncation may result in wrong results after substitution. To fix that, we add a supertype check to WHERE substitution, just like we do for GROUP BY/ORDER BY substitution. However, this fix can be too strict in some cases. For example, if the condition is WHERE <vcol_expr> > 21 where <vcol_expr> has type BIGINT and <vcol_field> has type TINYINT, the supertype check would fail, but here, data truncation would not result in wrong result, because the truncation clamps at extreme values which are -128 or 127 for TINYINT. The key is that the constant compared to is strictly inside the domain of vcol_field. To that end, we add a fallback check for WHERE substitution, that compares vcol_field type domain with the constants. A hole remains for strings with PAD SPACE collations: any const string for comparison could be a result of truncation, even if much shorter than the capacity of the vcol field. For example, for a table with (a varchar(20), vc varchar(5) as (concat('x',a)), index(vc)) vc = 'xabc' evals to 1 for a row with a = 'abc z' but concat('x',a) = 'xabc' evals to 0. The right hand side is 'xabc' could match a vc of varchar(N) for any N >= 4 because there could be arbitrary number of spaces before the truncation. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Marko Mäkelä
marko.makela@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
WIP MDEV-41010 BACKUP SERVER TO is slower than mariadb-backup mariadb-backup --backup always uses a dedicated log_copying_thread() that eagerly copies the log from the server. Let us do the same in multi-threaded BACKUP SERVER TO. FIXME: revise the HAVE_INNODB_PMEM code path and remove log_mutex. backup_sink::id: The thread identifier (0 to CONCURRENT-1) InnoDB_backup::context::tracked: Queue of log files to be copied. 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: buffer it for log_track_pmem()) |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||