Console View
|
Categories: connectors experimental galera main |
|
| connectors | experimental | galera | main | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Marko Mäkelä
marko.makela@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fil_space_t::backup_batch_size(): Return 1 MiB in pages. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Vladislav Vaintroub
vvaintroub@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
CA: get MSI properties of any length, do not truncate them GetProperty() helper instead of MAX_PATH buffers with ignored return values. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Aleksey Midenkov
midenok@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-17613 Refactor prune_partitions() to narrow read_partitions instead of full rewrite read_partitions is reset to all partitions (or the PARTITION() set) once per statement in open_table() (set_partition_bitmaps()), and prune_partitions() only intersects it with lock_partitions and the pruned set instead of rebuilding it. mark_all_partitions_as_used() is dropped. Pure refactoring, results unchanged. It lets a restriction set on read_partitions before pruning survive it - needed when read_partitions is pre-restricted, e.g. a versioned table whose history is partitioned reads only the current partition. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
sjaakola
seppo.jaakola@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-39958 Galera support for --binlog-storage-engine phase 1 of the development Galera did not support the binlog-in-server implementation, server refused to start with the combination. This patch is phase 1 work to make the basic replication to work and removes the startup check fpr rejecting binlog-in-engine use in cluster. There is one limitation: a transaction larger than the binlog cache can not be replicated yet. New mtr test galera.galera_binlog_in_engine runs a 2-node cluster with --binlog-storage-engine=innodb, node_2 joining through rsync SST. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Vladislav Vaintroub
vvaintroub@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CA: ERROR_INSTALL_FAILURE on failure, Log() as a function, W functions only; CMake: name the failed WiX step | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Oleksandr Byelkin
sanja@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-32401 expression cache lead to crash if table of wrong type created and the cache switched off 1) take into account TMP_TABLE_ALL_COLUMNS when we are modifying agg_item->result_field 2) remove unused now "bool materialized_subquery;" Co-Authored-By: Claude Opus 5.5 <[email protected]> |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Marko Mäkelä
marko.makela@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Merge 12.3 to 13.1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Maximilian Krög
maximilian.kroeg@geocept.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-40417 A column with compression enabled automatically adds DEFAULT '' Compression is stored in unireg_check as Field::TMYSQL_COMPRESSED, but has_default_function() treated any unireg_check other than Field::NONE as a default function. A COMPRESSED NOT NULL column without an explicit DEFAULT therefore never got NO_DEFAULT_VALUE_FLAG, the FRM was written without FIELDFLAG_NO_DEFAULT, and the column silently became optional while SHOW CREATE TABLE displayed a phantom DEFAULT ''. Exclude TMYSQL_COMPRESSED in has_default_function() and use that method in Column_definition::check() as well, so that both places which set NO_DEFAULT_VALUE_FLAG stay in sync. Tables created before the fix keep the wrong implicit DEFAULT '' in their FRM and are not repaired automatically, the affected columns need an explicit ALTER TABLE ... MODIFY. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Aleksey Midenkov
midenok@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-25529 cleanup for vers_set_starts() and starts_clause Extracts the STARTS-alignment logic into vers_set_starts() so it can be reused. Also adds the Vers_part_info::starts_clause flag in preparation for later commits. The flag records whether STARTS was given explicitly (by the user or in the unpacked frm), which the auto-partitioning code uses to decide whether to re-align STARTS to the existing history. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Marko Mäkelä
marko.makela@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fixup! 4898afa8c169aee679661e6848320a7f1c9d890a | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Sergei Golubchik
serg@mariadb.org |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-41491 parsec ASAN crash on manually modified mysql.global_priv table the server didn't invoke auth plugin's preprocess_hash() for empty passwords, thus it didn't check whether empty passwords were acceptable and plugin's auth() method reliance on the binary hash being correctly preprocessed was broken. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Vladislav Vaintroub
vvaintroub@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-33480 Windows MSI installer, convert from WIX v3 to supported versions Support both modern WiX (wix.exe, v5/v6) and legacy v3.x toolsets. WXS sources stay in v3 format, the modern toolset builds them after on-the-fly 'wix convert'. Modern WiX is preferred when installed, use WITH_WIX3=1 to force legacy. WIX_ACCEPT_EULA=ON accepts the EULA of WiX 7 and later, if WiX asks for it. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Marko Mäkelä
marko.makela@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fixup! 4898afa8c169aee679661e6848320a7f1c9d890a | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Vladislav Vaintroub
vvaintroub@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-33480 Windows MSI installer, convert from WIX v3 to supported versions Support both modern WiX (wix.exe, v5/v6) and legacy v3.x toolsets. WXS sources stay in v3 format, the modern toolset builds them after on-the-fly 'wix convert'. Modern WiX is preferred when installed, use WITH_WIX3=1 to force legacy. WIX_ACCEPT_EULA=ON accepts the EULA of WiX 7 and later, if WiX asks for it. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Aleksey Midenkov
midenok@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MDEV-25529 set_up_default_partitions() ER_OUT_OF_RESOURCES error | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Vladislav Vaintroub
vvaintroub@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-33480 Windows MSI installer, convert from WIX v3 to supported versions Support legacy WiX v3.x (default) and modern WiX (wix.exe, v5 or later). WXS sources stay in v3 format, modern WiX (-DWITH_WIX3=OFF) builds them after on-the-fly 'wix convert'. WIX_ACCEPT_EULA=ON accepts the EULA of WiX 7 and later, if WiX asks for it. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Vladislav Vaintroub
vvaintroub@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-39533 Resolve reparse points and enforce MY_NOSYMLINKS on Windows On Windows, my_realpath() only called GetFullPathName(), which doesn't resolve symlinks, junctions or mount points, unlike POSIX realpath(). At the same time, my_open() and my_delete() ignored MY_NOSYMLINKS entirely, so Windows silently lacked the symlink-attack protection MyISAM/Aria rely on for DATA DIRECTORY/INDEX DIRECTORY. Fix my_realpath() to resolve reparse points via CreateFile() + GetFinalPathNameByHandle(). A missing path now correctly returns 1/ENOENT on Windows too, matching POSIX. Make my_open()/my_delete() honor MY_NOSYMLINKS by comparing the caller-supplied (already-resolved) name against the opened handle's resolved path, mirroring the realpath()-equality branch of POSIX's NOSYMLINK_FUNCTION_BODY macro, rejecting a mismatch with ENOTDIR. Known limitation: GetFinalPathNameByHandle(FILE_NAME_NORMALIZED) can fail on some SMB shares; both my_realpath() and MY_NOSYMLINKS then fall back to the weaker FILE_NAME_OPENED query, logging a one-time warning naming the affected path. O_NOFOLLOW was plain 0 on Windows, so MyISAM/Aria's data-file open never actually got MY_NOSYMLINKS (only the index file did); it's now a real, reserved bit so that gating works as intended. my_win_unlink() (moved from my_delete.c to my_winfile.cc) now verifies no symlinks before any delete disposition is set, renames through the handle rather than by path, and reports real posix-delete errors (e.g. a read-only file) instead of masking them with a fallback attempt. Add unittest/mysys/my_symlink-t.c and extend my_delete-t.c with my_win_unlink()'s own fallback paths. Co-Authored-By: Claude Sonnet 5 <[email protected]> |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Aleksey Midenkov
midenok@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-25529 Auto-create: Pre-existing historical data is not partitioned as specified by ALTER Adds logic into prep_alter_part_table() for AUTO to check the history range (vers_get_history_range()) and based on (max_ts - min_ts) difference compute the number of created partitions and set STARTS value to round down min_ts value (vers_set_starts()) if it was not specified by user or if the user specified it incorrectly. In the latter case it will print warning about wrongly specified user value. In case of fast ALTER TABLE, f.ex. when partitioning already exists, the above logic is ignored unless FORCE clause is specified. When user specifies partition list explicitly the above logic is ignored even with FORCE clause. vers_get_history_range() detects if the index can be used for row_end min/max stats and if so it gets it with ha_index_first() and HA_READ_BEFORE_KEY (as it must ignore current data). Otherwise it does table scan to read the stats. There is test_mdev-25529 debug keyword to check the both and compare results. A warning is printed if the algorithm uses slow scan. Static key_cmp was renamed to key_eq to resolve compilation after key.h was included as key_cmp was already declared there. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Jan Lindström
jan.lindstrom@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Merge 10.11 to 11.4. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Aleksey Midenkov
midenok@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
WITHOUT_ABI_CHECK followup Followup for b337e14440b as info_src takes time too. info_src does not make much sense without ABI check. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Aleksey Midenkov
midenok@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MDEV-39306 Comments on update_virtual_fields() modes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Aleksey Midenkov
midenok@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-25529 Auto-create: Pre-existing historical data is not partitioned as specified by ALTER Adds logic into prep_alter_part_table() for AUTO to check the history range (vers_get_history_range()) and based on (max_ts - min_ts) difference compute the number of created partitions and set STARTS value to round down min_ts value (vers_set_starts()) if it was not specified by user or if the user specified it incorrectly. In the latter case it will print warning about wrongly specified user value. In case of fast ALTER TABLE, f.ex. when partitioning already exists, the above logic is ignored unless FORCE clause is specified. When user specifies partition list explicitly the above logic is ignored even with FORCE clause. vers_get_history_range() detects if the index can be used for row_end min/max stats and if so it gets it with ha_index_first() and HA_READ_BEFORE_KEY (as it must ignore current data). Otherwise it does table scan to read the stats. There is test_mdev-25529 debug keyword to check the both and compare results. A warning is printed if the algorithm uses slow scan. Static key_cmp was renamed to key_eq to resolve compilation after key.h was included as key_cmp was already declared there. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Marko Mäkelä
marko.makela@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
squash! e0ae58b3f720814805bec494262f3a3ebc0467f6 InnoDB_backup::context::tracked: Correctly track the log files. InnoDB_backup::archive_create(), innodb_backup_archive_create(): Hook for log_t::archive_create() |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Aleksey Midenkov
midenok@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-17613 Read sole now-partition for non-historical versioned query When a versioned table stores its history in partitions, an implicit query (no FOR SYSTEM_TIME) needs only current rows, which all live in the current partition. vers_setup_conds() no longer adds a row_end condition for such a table, and prune_partitions() restricts read_partitions to the current partition instead. read_partitions is reset to all partitions every statement in open_table(), so the restriction must be applied on each execution. prune_partitions() runs on each execution, also for merged derived tables and views, where vers_setup_conds() is not called again. Both functions guard with !vers_conditions.was_set() rather than !is_set(): set_all() sets type (flips is_set()) but not orig_type (was_set()), so is_set() would stay true across prepared-statement / stored-procedure re-executions and skip the restriction, leaving history partitions readable - rows matched then grow on each execute/call. A query whose time comes from @@system_versioning_asof must still read history, so the restriction is also skipped when from_sysvar is set. init_from_sysvar() sets this new flag rather than orig_type, so was_set(), print() and the write lock check still see only an explicit FOR SYSTEM_TIME. Like orig_type, the flag persists to prepared-statement re-execution. EXPLAIN no longer shows "Using where" for such a query: correctness now comes from partition selection rather than a row_end filter. With sql_safe_updates, DELETE without WHERE on such a table is now rejected even with LIMIT, as for a non-versioned table. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Lawrin Novitsky
lawrin.novitsky@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-16670 Improve execute direct (prepare and execute) performance The patch detects if the processed PREPARE command is first part of the pipelined DIRECT+EXECUTE commands with stmt id -1 for execution. For that the vio class has been extended with peek method that lets us verify that the server has already received next command, it is EXECUTE with stmt id -1, and the packet is minimally complete. If it is so, the execution is forwarded to the fused prepare+execute path as if it was a single command. It lets a statement's execute leg run before any real prepare/check_prepared_statement() has happened and the prepare leg does basically query parsing only. Several places downstream assumed that had already happened, so give Query_arena a single predicate, is_direct_exec_unresolved() - true only for the one-shot fused pass that never went through a real prepare - and fix each broken assumption against it: * Query cache: a cached response is replayed without ever running mysql_execute_command(), so the buffered prepare-response hook never fires; conversely, populating the cache captures that hook's bytes too (net_real_write() sees the whole flushed buffer), poisoning the entry for unrelated later clients. Direct-exec statements now opt out of the query cache entirely (query_cache_is_applicable / lex->safe_to_cache_query, saved and restored around just that one execution). Covered by test_mdev_16670_qc_hit_hang and test_mdev_16670_qc_poison. * Reprepare: a mid-execute reprepare() (e.g. ER_NEED_REPREPARE) swaps in a lex that DID go through a real prepare, so the statement is no longer "unresolved" once that happens - tracked via a new IS_DIRECT_EXEC_REPREPARED flag so the direct-exec special-casing only applies to the original pass. * System versioning / periods: skip_setup_conds() treated the fused pass as an ordinary re-execution rather than a first resolution, so vers_setup_conds()/period_setup_conds() never ran - a versioned table or FOR PORTION OF statement used the wrong predicate. Now counted as "first execute" via is_direct_exec_unresolved(). Covered by test_mdev_16670_versioned_rows and test_mdev_16670_trigger_versioned. * Unsupported statements check used to be done as part of the check_prepared_statement, has been moved to the new check_stmt_command_supported function called from the Prepared_statement::prepare method. Covered by test_mdev_16670_unsupported_ps. * CALL statements and queries referencing a stored function are excluded from the optimized path (skip_direct_exec_optimization()): their nested sub-statements run with thd->cur_stmt still pointing at the outer statement, which would otherwise misfire the buffered prepare-response hook. The flag is reset for them after the query parsing. Covered by test_mdev_16670_demoted_function_call. Also adds regression tests for the reply-pair on parse error (test_mdev_16670_parse_error_reply_pair), SQLSTATE/errno preservation through the execute-leg fallback (test_mdev_16670_fallback_sqlstate), general_log coverage (test_mdev_16670_general_log), a statement killed mid-fallback (test_mdev_16670_kill_during_fallback), and prebind usage of mariadb_stmt_execute_direct() with real bound parameters (test_mdev_16670_prebind_direct). It's possible to compile with global variable opt_exec_direct that controls if the optimization should be used. Its existence can be switched on using cmake option WITH_DIRECT_EXEC_OPTIMIZE_SWITCH. By default it does not exist, as it is believed to be unneeded in the general case. Assisted-by: Claude Sonnet 5 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Vladislav Vaintroub
vvaintroub@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-33480 Windows MSI installer, convert from WIX v3 to supported versions Custom actions : get rid of wcautil and dutil library dependencies They are not an official part of WiX distribution anymore. Replace the functionality with native MSI api, the Wix helpers we used were thin wrappers anyway. Also replace the VBScript FakeFailure custom action with a native one, VBScript is deprecated in Windows and scheduled for removal. Read MSI properties with a helper that handles any length and reports errors. Values are no longer silently truncated to MAX_PATH, e.g a long data directory could be cut to its parent, and then removed. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Aleksey Midenkov
midenok@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-25529 Timestamp_string for printing timestamps Add a Timestamp_string helper that formats a Timestamp in the session time zone for use in warning and error messages, plus THD::timestamp_to_string() and a Temporal_hybrid constructor behind it. Reuse the existing Timestamp type rather than introducing a new one. Use it when printing STARTS and the history range. Rename the error symbol ER_PART_STARTS_BEYOND_INTERVAL to WARN_VERS_STARTS_BEYOND_INTERVAL and extend its message: it now takes the STARTS timestamp and the query timestamp as arguments, so the number of format arguments changes from one to three. The error number (4164) is unchanged, so this is not an ABI break, but libmariadb still exports the old symbol name for that number until its submodule is updated. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Vladislav Vaintroub
vvaintroub@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-33480 Windows MSI installer, convert from WIX v3 to supported versions Support both modern WiX (wix.exe, v5/v6) and legacy v3.x toolsets. WXS sources stay in v3 format, the modern toolset builds them after on-the-fly 'wix convert'. Modern WiX is preferred when installed, use WITH_WIX3=1 to force legacy. WIX_ACCEPT_EULA=ON accepts the EULA of WiX 7 and later, if WiX asks for it. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Aleksey Midenkov
midenok@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-40380 vcol.races sporadic timeout After MDEV-39261 purge opens TABLE via open_purge_table() for any undo record of a table with indexed virtual columns, including the insert undo record. If purge does that after FLUSH TABLES, the TABLE is put back into the table cache. Then DELETE from con1 reuses the cached TABLE, skips open_table_from_share() and never hits the TABLE_after_field_clone sync point. DELETE completes immediately, wait_condition times out and SELECT returns an empty set. The fix waits for purge to complete before FLUSH TABLES. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Aleksey Midenkov
midenok@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-17613 Test cleanup: sorted result for versioning.partition Avoid result fluctuations with different storage engines. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Alessandro Vetere
iminelink@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-32286 Reuse remembered clustered leaves in secondary-index scans Row_sel_get_clust_rec_for_mysql::operator() descends the clustered B-tree from the root for every row whose clustered-index record a secondary-index scan must read, although consecutive rows land on the same clustered leaf page wherever the secondary order tracks the clustered one. A non-covering scan needs one for every row, a locking read needs one whatever the secondary index holds, because an exclusive select lock type makes ha_innobase::build_template() build its template against the clustered index, and a covering scan needs one for every row of a secondary leaf whose PAGE_MAX_TRX_ID its read view cannot see. ANALYZE FORMAT=JSON charges each descent its full height, and those descents are nearly the whole cost: the secondary index is charged its own descent and one page for each further leaf, and nothing per row, because the position that its cursor holds between two rows is restored optimistically, which latches the leaf again without counting an access. So pages_accessed is the row count times the height of the clustered index, plus a handful: 1000 rows over a 2-level clustered index cost 2006 and 750 rows over a 3-level one cost 2291, where a full table scan of the same data costs 23 and 110. Let a handle remember the clustered leaves that the lookups of one statement reached, and let the next lookup try them before it descends again. The reasoning behind each value and each rejection is in the comments beside it. row0mysql.h defines clust_leaf_hint_slot, which names one leaf: its page number, copies of its first and last user record truncated to the key fields, which bound the key range that the leaf held when it was remembered, and the rec_get_offsets() of both. A slot of a leaf that had no right sibling names no last key, because every key above the last record of the rightmost leaf still belongs to it. CLUST_LEAF_HINT_SLOTS (4) slots hang off the new row_prebuilt_t::clust_leaf_hint, beside clust_leaf_hint_mru, the most recently used order held as slot numbers, and clust_leaf_hint_n and clust_leaf_hint_miss, the used-slot count and the miss counter. row0sel.cc holds the policy. row_sel_clust_leaf_hint_covers() compares a key against the remembered ranges, so a lookup that no slot can answer costs no buffer pool access and no pages_accessed. row_sel_clust_leaf_hint_search() probes the first slot that covers the key and moves it to the front of the order. row_sel_clust_leaf_hint_remember() records the leaf that a descent landed on, and refreshes the slot of a leaf that is remembered already rather than spend a second one on the same page. Two descents fill no slot: the lookups of a statement before lookup CLUST_LEAF_HINT_MIN_LOOKUPS (4), and a leaf that is the root. row_sel_clust_leaf_hint_armed() stands a scan down once the slots stop paying for themselves: a miss adds CLUST_LEAF_HINT_MISS_WEIGHT (2) to the miss counter and a hit takes one away, so a scan gives the slots up where it answers too little of its lookups to pay for them, CLUST_LEAF_HINT_MAX_MISSES (8) misses with no hit between them still reach the threshold, and one lookup in CLUST_LEAF_HINT_RETRY (1024) starts the count again, so a scan whose order becomes correlated only later recovers. Both halves of the cost stop there, the test of the slots and the copies that refresh them. Row_sel_get_clust_rec_for_mysql::operator() calls all of this in place of its btr_pcur_open_with_no_init(), and only where the adaptive hash index is disabled. A guess of that index lands on the record with no page-local search and no page access to charge, but neither of the two is faster for every shape of index and scan, and a hint that runs first stops that index from adding entries. The adaptive hash index is off by default, so the hints are active in a default configuration. btr0cur.h and btr0cur.cc add btr_cur_t::try_leaf_hint(), a PAGE_CUR_LE, BTR_SEARCH_LEAF search on one named leaf, which starts from the record where the last search of that leaf landed while that record is still valid, and from a binary search of the page otherwise. It acquires the page with buf_page_try_get(): a hint is never derived from a latched parent page, so by the time it is tried it can precede the caller's already-latched secondary-index leaf in the latching order, where a blocking wait can deadlock. It then rejects the page unless the checks that it makes on the latched frame put the match on it. Those checks are the sole authority on the result, so a stale range costs a wasted probe or a needless descent, never a wrong result, and the ranges need no invalidation protocol. A slot also holds a btr_leaf_step, where the last search of its leaf landed: the block, its buf_block_t::modify_clock and the record. A record pointer stays valid while the block holds the same page and the clock has not moved, because eviction, deletion and reorganization advance the clock and an insertion moves no record. While the step is valid, btr_cur_t::try_leaf_hint() starts from that record instead of a binary search of the page. page0cur.cc adds page_cur_search_near(), which compares the key with the record and with one neighbour, and positions the cursor on the record, on the record after it or on the record before it. It refuses any other answer and leaves it to page_cur_search_with_match(). So a scan whose consecutive rows are neighbours in the clustered index finds each row with two comparisons. A scan that reads the clustered leaf in falling key order does the same: a secondary key that falls as the primary key rises, or a secondary index read backward. Where the secondary order tracks the clustered one with gaps between the rows, the step fails, and btr_leaf_step::expect (below) leaves the leaf to the binary search. The step refuses the metadata pseudo-record, which compares below every key on 0 fields, and a record before it that is the infimum. A debug build checks every successful step against the binary search. A failed step costs comparisons whose outcome no branch predictor can guess, so btr_leaf_step::expect gates it. The next search tries the step only where this one landed where a step would have: on the remembered record, or right before or after it. The successor link of the landing record tells the record right before, because finding a predecessor takes a walk of the page directory. So a scan in random order over a few leaves, which the slots answer in full, keeps the binary search and pays only the tests that set expect. row_sel_clust_leaf_hint_remember() sets the step on the record where a descent landed, with expect set. ha_innodb.cc: ha_innobase::reset() zeroes the used-slot count and the miss counter per statement, matching autoinc_last_value. row0mysql.cc: row_prebuilt_free() frees the key buffers that the slots own. innodb.clust_leaf_hint measures pages_accessed over key orders that differ in how closely the secondary order tracks the clustered one, and further tables check query results over the record formats and key shapes that a clustered-index lookup has to read, down to the metadata pseudo-record of instant ALTER TABLE, to leaves that split and merge while a locking read walks them, and to a record that a remembered leaf supplies for a scan that must then rebuild an older version of it. Two of the tables scan a covering index, which reads a clustered record under an exclusive select lock type and under a PAGE_MAX_TRX_ID that the read view cannot see. clust_leaf_hint_off_debug runs the same body with the hints turned off, through a debug switch that returns before a lookup tests or refreshes the slots, so a diff of the two .result files is what the hints save: 2006 to 1031 (2-level clustered index), 2291 to 1011 (3-level), 4006 to 2015 (two interleaved key ranges), 12016 to 9078 (locality in the second half alone) and 20020 to 10045 for a covering scan that FOR UPDATE makes non-covering, where the same scan without FOR UPDATE costs 20 in both files. Two orders with too little locality to pay for the slots give them up early and end within a hundred accesses of the unhinted count: 20020 to 19966 (decorrelated) and 20020 to 19999 (shuffled). Two further tables in innodb.clust_leaf_hint cover the step back. t16 scans a secondary index whose keys fall as the primary key rises, and reads an ascending secondary index backward: 20020 to 10047 for both. t17 runs the inverse scan on ROW_FORMAT=REDUNDANT with an instantly added column. innodb.clust_leaf_hint_backward_debug reaches the two refusals of the step back that no count shows, at the infimum and at the metadata pseudo-record. A lookup reaches either only for a key that left the clustered index while the slot still covers it. The rollback of an insert over a delete-marked record removes the key while purge leaves the secondary entry, and a PAGE_MAX_TRX_ID that the read view cannot see makes the reader look that entry up. A debug switch writes each refusal to the error log, and the case reads both back. A slot stays valid when dict_index_t::clear_instant_alter() changes the index under a reader that holds a shared metadata lock, between two lookups or during one. That clear runs where purge or rollback removes the last user record of a single-page table whose only instant change was ADD COLUMN, and it raises dict_index_t::n_core_fields. rec_copy_prefix_to_buf() writes the copies in the format of an index without instant ADD COLUMN, their offsets are made with them, and the comparisons depend on the key fields alone, which no instant change moves. So the slots do not record n_core_fields, and the hints read it only under a latch on a clustered leaf. innodb.clust_leaf_hint_instant_alter makes a lookup compare against a slot that a clear has outlived. The clear needs the clustered index to lose the last user record of its root page, while no leaf that is the root fills a slot, so the tree has to shrink between the two, which purge does there. The reader therefore reads uncommitted rows at READ UNCOMMITTED and waits in a stored function while a rollback and purge take them away, and one row that arrives above the position it stopped at is the lookup that tests the slots. main.rowid_filter_innodb: 90 to 88, and its ahi combination unchanged. A static_assert in Row_sel_get_clust_rec_for_mysql::operator() fails to compile if btr_search.enabled stops being a Boolean that applies to every index, because the test !btr_search.enabled that stands the hints down would then have to become !btr_search.is_enabled(clust_index). |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Vladislav Vaintroub
vvaintroub@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-33480 Windows MSI installer, convert from WIX v3 to supported versions Custom actions : get rid of wcautil and dutil library dependencies They are not an official part of WiX distribution anymore. Replace the functionality with native MSI api, the Wix helpers we used were thin wrappers anyway. Also replace the VBScript FakeFailure custom action with a native one, VBScript is deprecated in Windows and scheduled for removal. Read MSI properties with a helper that handles any length and reports errors. Values are no longer silently truncated to MAX_PATH, e.g a long data directory could be cut to its parent, and then removed. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Sergei Petrunia
sergey@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Disable -Wredundant-move warnings in storage/rocksdb The warnings were in RocksDB (not MyRocks) and looked like this: rocksdb/db/db_impl/db_impl.cc: In member function ‘virtual rocksdb::Status rocksdb::DBImpl::FlushWAL(bool)’: rocksdb/db/db_impl/db_impl.cc:1329:23: warning: redundant move in return statement [-Wredundant-move] 1329 | return std::move(io_s); | ~~~~~~~~~^~~~~~ |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Yuchen Pei
ycp@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-41064 Use my_safe_alloca for unescaped json server option string This fixes segv when the option string is longer than thread stack |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Jan Lindström
jan.lindstrom@mariadb.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Merge 10.11 to 11.4. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Aleksey Midenkov
midenok@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-17613 bitmap_range() for iteration over partition bitmap Returns bit range for partition element. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Aleksey Midenkov
midenok@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-39306 Index corruption on UPDATE when using versioned InnoDB table (on UPDATE) Duplicated by: MDEV-39711 UPDATE on System-Versioned Tables uses the after-value in Row-format Binlog's before-image MDEV-24646 ASAN heap-use-after-free in Field_blob::pack / THD::binlog_update_row upon DML on table with virtual column vers_update_fields() recomputed virtual fields with VCOL_UPDATE_FOR_READ. For BLOB virtual columns this mode swaps Field_blob::value and read_value after the computation (swap_values). After the swap, Field_blob::value holds the buffer that record[1] (the old row image) points to. The next VCOL_UPDATE_FOR_WRITE computation, done by fill_record() right after vers_update_fields(), stores the new value into that buffer and so overwrites the old row image. As a result, InnoDB builds the old secondary-index key from the new value (MDEV-39306), and the row-format binlog writes the new value into the before-image (MDEV-39711), or a freed buffer (MDEV-24646). The fix removes the virtual fields computation from vers_update_fields(). Its callers now recompute virtual fields with VCOL_UPDATE_FOR_WRITE after the system fields are set: fill_record() already did, while update_generated_fields(), multi_update::do_updates() and the RBR appliers do it explicitly. multi_update::do_updates() still computes virtual fields twice per row: before BEFORE triggers and after vers_update_fields(), since the system fields cannot be set before compare_record(). Based on the MDEV-24646/MDEV-39711 fix by ParadoxV5 (0c2f3de1a2d), which switched vers_update_fields() to VCOL_UPDATE_FOR_WRITE. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Aleksey Midenkov
midenok@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-30441 ASAN heap-use-after-free in Field_blob::pack/pack_row When InnoDB computes an indexed virtual column (e.g. the long unique hash), TABLE::update_virtual_field() walked its expression with update_vcol_processor() and recomputed every vcol it referenced, including STORED ones. innobase_allocate_row_for_vcol() saves and restores Field_blob::value only for non-stored blob vcols, so recomputing a STORED blob vcol in Field_blob::store_field() did val_str(&value) and freed the buffer that record[0] still pointed to. The row-format binlog then packed record[0] from freed memory. The buffer is allocated only when the value is converted, so the bug needs a character set conversion (e.g. BLOB to utf8mb3 TEXT). The fix skips STORED vcols in update_vcol_processor(). Their values are already in the record: InnoDB copies them as base columns of the virtual column, and the SQL layer computes them before writing. The test cases detect the bug only under ASAN or Valgrind. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Aleksey Midenkov
midenok@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-17613 Refactor prune_partitions() to build into a dedicated bitmap prune_partitions() built the used-partition set directly in read_partitions: it cleared it, let find_used_partitions*() fill it, and in the index-merge case used it as scratch too - both buffer and result. The patch adds PART_PRUNE_PARAM::parts_bitmap and builds the return into it instead. read_partitions is produced from it only at the end: copied when the condition was analyzed (IMPOSSIBLE included, parts_bitmap left empty), or set to all locked partitions via mark_all_partitions_as_used() when nothing could be pruned. Pure refactoring: read_partitions ends up identical on every path. It decouples the pruning scratch from read_partitions, so a later commit can switch the final bitmap_copy() to bitmap_intersect() - letting pruning narrow a pre-set read_partitions (e.g. a versioned table selecting only its current partition) instead of overwriting it. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Aleksey Midenkov
midenok@gmail.com |
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
MDEV-25529 ALTER TABLE FORCE syntax improved Lets an alter_list be supplied alongside a partitioning or remove_partitioning clause in either order. This allows the FORCE clause to be added to an existing command, and lets AUTO and FORCE be specified together, which later commits of this task rely on. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||