diff --git a/CMakeLists.txt b/CMakeLists.txt
index b98d19db5b..e4a641835a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -170,8 +170,11 @@ ENDIF()
 IF(MY_HOST_SYSTEM_NAME MATCHES "Linux")
   # Trust 'rpm -qf /' rather than 'uname -s'
   STRING(REGEX MATCH "\\.el([6789])\\." MATCH_FSYS "${MY_HOST_FILESYSTEM_NAME}")
+  IF(NOT CMAKE_MATCH_1)
+    STRING(REGEX MATCH "\\.el(10)\\." MATCH_FSYS "${MY_HOST_FILESYSTEM_NAME}")
+  ENDIF()
 
-  # Set LINUX_RHEL6, LINUX_RHEL7, LINUX_RHEL8 or LINUX_RHEL9
+  # Set LINUX_RHEL6, LINUX_RHEL7, LINUX_RHEL8 or LINUX_RHEL9 or LINUX_RHEL10
   IF(CMAKE_MATCH_1)
     SET(LINUX_RHEL 1)
     SET(LINUX_RHEL${CMAKE_MATCH_1} 1)
@@ -227,7 +230,7 @@ IF(CMAKE_HOST_UNIX AND NOT FORCE_UNSUPPORTED_COMPILER
     IF(LINUX_RHEL)
       MESSAGE(STATUS "This is ${MATCH_FSYS} as found from 'rpm -qf /'")
     ENDIF()
-    IF(LINUX_RHEL)
+    IF(LINUX_RHEL AND NOT LINUX_RHEL10)
       MESSAGE(STATUS "Looking for a devtoolset compiler")
       IF(LINUX_RHEL6)
         SET(ALTERNATIVE_PATHS "/opt/rh/devtoolset-8")
@@ -240,6 +243,8 @@ IF(CMAKE_HOST_UNIX AND NOT FORCE_UNSUPPORTED_COMPILER
         ENDIF()
       ELSEIF(LINUX_RHEL8 OR LINUX_RHEL9)
         SET(ALTERNATIVE_PATHS "/opt/rh/gcc-toolset-13")
+      ELSEIF(LINUX_RHEL10)
+        # Use default /usr/bin/gcc which is 14.2.1
       ENDIF()
 
       FOREACH(OPT_PATH ${ALTERNATIVE_PATHS})
@@ -465,6 +470,11 @@ set(CMAKE_MODULE_PATH
     ${CMAKE_MODULE_PATH}
 )
 
+IF(LINUX_RHEL10 AND MY_HOST_MACHINE_NAME MATCHES "aarch64")
+  SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
+  MESSAGE(STATUS "Building with PIC")
+ENDIF()
+
 include(TestBigEndian)
 TEST_BIG_ENDIAN(IS_BIG_ENDIAN)
 add_definitions(-DIS_BIG_ENDIAN=${IS_BIG_ENDIAN})
@@ -818,7 +828,6 @@ IF (JIT_EXECUTOR_LIB)
   FIND_LIBRARY(JIT_EXECUTOR_LIBRARY
     NAMES
       jitexecutor
-      libjitexecutor
     HINTS
       "${JIT_EXECUTOR_LIB}"
     NO_DEFAULT_PATH)
@@ -1076,11 +1085,13 @@ IF(MYSQL_BUILD_DIR)
 
   IF(AUTH_CLIENT_PLUGINS)
     SET(AUTH_CLIENT_PLUGINS_COPY ${AUTH_CLIENT_PLUGINS})
-    SET(EXPECTED_AUTH_CLIENT_PLUGINS "authentication_ldap_sasl_client" "authentication_oci_client" "mysql_native_password" "authentication_openid_connect_client")
-
-    IF(NOT APPLE)
-      LIST(APPEND EXPECTED_AUTH_CLIENT_PLUGINS "authentication_kerberos_client")
-    ENDIF()
+    SET(EXPECTED_AUTH_CLIENT_PLUGINS
+      "authentication_kerberos_client"
+      "authentication_ldap_sasl_client"
+      "authentication_oci_client"
+      "authentication_openid_connect_client"
+      "mysql_native_password"
+    )
 
     IF(${OPENSSL_MAJOR_MINOR_FIX_VERSION} VERSION_GREATER_EQUAL "1.1.1")
       IF(${MYSQL_VERSION} VERSION_GREATER_EQUAL "8.2.0")
diff --git a/LICENSE b/LICENSE
index e299ff948b..453b4952af 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 Licensing Information User Manual
 
-MySQL Shell, part of MySQL 9.3.0 Community
+MySQL Shell, part of MySQL 9.4.0 Community
      __________________________________________________________________
 
 Introduction
@@ -8,18 +8,18 @@ Introduction
    This License Information User Manual contains Oracle's product license
    and other licensing information, including licensing information for
    third-party software which may be included in this distribution of
-   MySQL Shell, part of MySQL 9.3.0 Community.
+   MySQL Shell, part of MySQL 9.4.0 Community.
 
-   Last updated: March 2025
+   Last updated: June 2025
 
 Licensing Information
 
-   This release of MySQL Shell, part of MySQL 9.3.0 Community, is brought
+   This release of MySQL Shell, part of MySQL 9.4.0 Community, is brought
    to you by the MySQL team at Oracle. This software is released under
    version 2 of the GNU General Public License (GPLv2), as set forth
    below, with the following additional permissions:
 
-   This distribution of MySQL Shell, part of MySQL 9.3.0 Community, is
+   This distribution of MySQL Shell, part of MySQL 9.4.0 Community, is
    designed to work with certain software (including but not limited to
    OpenSSL) that is licensed under separate terms, as designated in a
    particular file or component or in the license documentation. Without
diff --git a/MYSQL_VERSION b/MYSQL_VERSION
index b70bd896e0..bae4b46d58 100644
--- a/MYSQL_VERSION
+++ b/MYSQL_VERSION
@@ -1,4 +1,4 @@
 MYSQL_VERSION_MAJOR=9
-MYSQL_VERSION_MINOR=3
+MYSQL_VERSION_MINOR=4
 MYSQL_VERSION_PATCH=0
 MYSQL_VERSION_EXTRA=
diff --git a/ROUTING_GUIDELINES.md b/ROUTING_GUIDELINES.md
index 4c076abaed..215fb8c907 100644
--- a/ROUTING_GUIDELINES.md
+++ b/ROUTING_GUIDELINES.md
@@ -29,9 +29,11 @@ This cookbook provides recipes for creating and managing Routing Guidelines with
 
 ## Recipe 1: Cluster: Default Routing Guideline
 
-In this setup, we'll create a default Routing Guideline for an InnoDB Cluster. The AdminAPI populates the guideline with default values, according to the topology type, which represent Router's default behavior on that topology.
+In this setup, we'll create a default Routing Guideline for an InnoDB Cluster.
 
-In this setup, we'll create a default Routing Guidelines for an InnoDB Cluster which was configured with the defaults. The guideline represents a basic setup where write operations are directed to the Primary member and read operations are distributed across Secondary nodes (if available) or the Primary node as a fallback. This setup provides a balanced approach that suits many common applications.
+When no custom guideline is provided, the AdminAPI generates a default configuration based on the topology type. This default reflects MySQL Router's default behavior for that topology.
+
+For InnoDB Cluster, the default guideline routes write operations to the Primary member and read operations to Secondary members, if available, falling back to the Primary when needed. This setup offers a balanced and resilient approach, suitable for many common workloads.
 
 ### Step 1: Create the guideline
 
@@ -113,7 +115,7 @@ Example JSON output:
             "name": "ro"
         }
     ],
-    "version": "1.0"
+    "version": "1.1"
 }
 ```
 
@@ -248,8 +250,8 @@ cluster.list_routers()
             "rwPort": "6446",
             "rwSplitPort": "6450",
             "rwXPort": "6448",
-            "supportedRoutingGuidelinesVersion": "1.0",
-            "version": "9.2.0"
+            "supportedRoutingGuidelinesVersion": "1.1",
+            "version": "9.3.0"
         },
         "domus::my_router2": {
             "currentRoutingGuideline": null,
@@ -261,8 +263,8 @@ cluster.list_routers()
             "rwPort": "6446",
             "rwSplitPort": "6450",
             "rwXPort": "6448",
-            "supportedRoutingGuidelinesVersion": "1.0",
-            "version": "9.2.0"
+            "supportedRoutingGuidelinesVersion": "1.1",
+            "version": "9.3.0"
         }
     }
 }
@@ -475,7 +477,7 @@ guideline = {
             "name": "compliance_based"
         }
     ],
-    "version": "1.0"
+    "version": "1.1"
 }
 ```
 
@@ -501,7 +503,6 @@ rg.set_destination_option("EU_Regions", "match", "$.server.address IN ('eu-west-
 
 // Disable the 'compliance_based' route temporarily
 rg.set_route_option("compliance_based", "enabled", False);
-
 ```
 
 In this example:
@@ -584,7 +585,7 @@ Save the following JSON configuration in a file, e.g., load_balancing_guideline.
             "name": "rw"
         }
     ],
-    "version": "1.0"
+    "version": "1.1"
 }
 ```
 
@@ -671,7 +672,7 @@ Application-specific and schema-based routing allows routing guidelines to direc
             "name": "data_schema_routing"
         }
     ],
-    "version": "1.0"
+    "version": "1.1"
 }
 ```
 
@@ -737,7 +738,7 @@ MySQL Version Specific Routing is useful when certain applications or sessions n
             "name": "ro_traffic_to_8_0_39"
         }
     ],
-    "version": "1.0"
+    "version": "1.1"
 }
 ```
 
@@ -807,7 +808,7 @@ Custom tag-based and performance-based routing enables traffic routing based on
             "name": "admin_finance_traffic"
         }
     ],
-    "version": "1.0"
+    "version": "1.1"
 }
 ```
 
@@ -909,7 +910,7 @@ In environments with distinct stages such as testing, staging, and production, r
             "name": "session_affinity"
         }
     ],
-    "version": "1.0"
+    "version": "1.1"
 }
 ```
 
@@ -1035,7 +1036,7 @@ Client characteristics routing allows traffic to be directed based on specific a
             "name": "testing_traffic"
         }
     ],
-    "version": "1.0"
+    "version": "1.1"
 }
 ```
 
@@ -1043,14 +1044,14 @@ Client characteristics routing allows traffic to be directed based on specific a
 
 With the Comprehensive_ConnectAttrs_Routing in place:
 
-  - linux_traffic: Routes sessions where the "_os" connection attribute is "Linux" to servers running on Linux, distributing connections in a round-robin fashion.
+  - linux_traffic: Routes sessions where the "_os" connection attribute is "Linux" to servers running on Linux ("Linux_Servers"), distributing connections in a round-robin fashion.
 
-  - x86_64_traffic: Routes sessions with the "_platform" attribute set to "x86_64" to x86_64-servers, using a first-available strategy.
+  - x86_64_traffic: Routes sessions with the "_platform" attribute set to "x86_64" to "x86_64_Servers", using a first-available strategy.
 
-  - backup_traffic: Routes sessions coming from "mysqldump" (connectAttrs.program_name = 'mysqldump') to the Backup servers.
+  - backup_traffic: Routes sessions coming from "mysqldump" (connectAttrs.program_name = 'mysqldump') to the "Backup_Servers".
 
-  - Commercial Traffic: Routes sessions using the 'audit' schema to servers designated as Commercial_Servers.
+  - Commercial Traffic: Routes sessions using the 'audit' schema to servers designated as "Commercial_Servers".
 
-  - Testing Traffic: Routes specific test sessions ($.session.user = 'test_user') to servers tagged as testing resources.
+  - Testing Traffic: Routes specific test sessions ($.session.user = 'test_user') to servers tagged for testing ("Testing_Servers").
 
 This guideline provides flexibility to route traffic based on client-specific attributes, enabling targeted resource allocation and optimization for diverse client characteristics.
diff --git a/cmake/apple_use_bundled_openssl.cmake b/cmake/apple_use_bundled_openssl.cmake
index b279e049ab..d89c1dbae0 100644
--- a/cmake/apple_use_bundled_openssl.cmake
+++ b/cmake/apple_use_bundled_openssl.cmake
@@ -1,4 +1,4 @@
-# Copyright (c) 2024, Oracle and/or its affiliates.
+# Copyright (c) 2024, 2025, Oracle and/or its affiliates.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License, version 2.0,
@@ -28,7 +28,9 @@ else()
 endif()
 
 FOREACH(bin ${binaries})
-  SET(pattern "@executable_path|@loader_path")
+  # the /usr/local/mysql/lib/ is present in dependencies of Kerberos libraries
+  # built in PB2
+  SET(pattern "@executable_path|@loader_path|/usr/local/mysql/lib/")
 
   IF(NOT bin MATCHES "${CRYPTO_VERSION}")
     STRING(APPEND pattern "|${CRYPTO_VERSION}")
diff --git a/modules/adminapi/base_cluster.cc b/modules/adminapi/base_cluster.cc
index 1772080ed0..ed83912ecd 100644
--- a/modules/adminapi/base_cluster.cc
+++ b/modules/adminapi/base_cluster.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2023, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -123,21 +123,6 @@ void Base_cluster::set_routing_option(const std::string &router,
       [&]() { base_impl()->set_routing_option(router, option, value); }, false);
 }
 
-shcore::Dictionary_t Base_cluster::routing_options(const std::string &router) {
-  assert_valid("routingOptions");
-
-  std::string type =
-      to_display_string(base_impl()->get_type(), Display_form::API_CLASS);
-
-  mysqlsh::current_console()->print_warning(shcore::str_format(
-      "This function is deprecated and will be removed in a future release of "
-      "MySQL Shell. Use <%s>.<<<routerOptions()>>> instead.",
-      type.c_str()));
-
-  return execute_with_pool(
-      [&]() { return base_impl()->routing_options(router); }, false);
-}
-
 shcore::Dictionary_t Base_cluster::router_options(
     const shcore::Option_pack_ref<Router_options_options> &options) {
   assert_valid("routerOptions");
diff --git a/modules/adminapi/base_cluster.h b/modules/adminapi/base_cluster.h
index 37f0378616..6e5a95f973 100644
--- a/modules/adminapi/base_cluster.h
+++ b/modules/adminapi/base_cluster.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -58,8 +58,6 @@ class Base_cluster : public shcore::Cpp_object_bridge {
   void set_routing_option(const std::string &router, const std::string &option,
                           const shcore::Value &value);
 
-  shcore::Dictionary_t routing_options(const std::string &router);
-
   shcore::Dictionary_t router_options(
       const shcore::Option_pack_ref<Router_options_options> &options);
 
@@ -240,21 +238,6 @@ It is possible to change password without affecting certificate options or
 vice-versa but certificate options can only be changed together.
 )*"));
 
-REGISTER_HELP_SHARED_TEXT(ROUTINGOPTIONS_HELP_TEXT, (R"*(
-Lists the <<<:Type>>> Routers configuration options.
-
-@param router Optional identifier of the router instance to query for the options.
-
-@returns A JSON object describing the configuration options of all router
-instances of the <<<:Type>>> and its global options or just the given Router.
-
-@attention This function is deprecated and will be removed in a future release
-of MySQL Shell. Use <<<:Type>>>.routerOptions() instead.
-
-This function lists the Router configuration options of all Routers of the
-<<<:Type>>> or the target Router.
-)*"));
-
 REGISTER_HELP_SHARED_TEXT(CREATEROUTINGGUIDELINE_HELP_TEXT, (R"*(
 Creates a new Routing Guideline for the <<<:Type>>>.
 
@@ -354,6 +337,12 @@ The following options are supported:
 @li force (boolean): Allows overwriting an existing Routing Guideline with the
 same name. Disabled by default.
 
+@li rename (string): Allows importing a guideline under a new name; for
+example, if a guideline with the same name already exists in the <<<:Type>>>.
+
+The options 'force' and 'rename' are mutually exclusive. Only one can be used
+at a time.
+
 For more information on Routing Guidelines, see \\? RoutingGuideline.
 )*"));
 
diff --git a/modules/adminapi/cluster/api_options.cc b/modules/adminapi/cluster/api_options.cc
index 472d96d796..865241b007 100644
--- a/modules/adminapi/cluster/api_options.cc
+++ b/modules/adminapi/cluster/api_options.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -35,15 +35,6 @@
 
 namespace mysqlsh::dba::cluster {
 
-namespace {
-void throw_rescan_mixing_exception() {
-  throw shcore::Exception::argument_error(shcore::str_format(
-      "Options '%s' and '%s' are mutually exclusive with deprecated options "
-      "'%s' and '%s'. Mixing either one from both groups isn't allowed.",
-      kAddUnmanaged, kRemoveObsolete, kAddInstances, kRemoveInstances));
-}
-}  // namespace
-
 const shcore::Option_pack_def<Add_instance_options>
     &Add_instance_options::options() {
   static const auto opts = std::invoke([]() {
@@ -132,10 +123,6 @@ const shcore::Option_pack_def<Options_options> &Options_options::options() {
 }
 
 namespace {
-constexpr shcore::Option_data<std::vector<mysqlshdk::db::Connection_options>>
-    kOptionAddInstances{"addInstances"};
-constexpr shcore::Option_data<std::vector<mysqlshdk::db::Connection_options>>
-    kOptionRemoveInstances{"removeInstances"};
 constexpr shcore::Option_data<bool> kOptionAddUnmanaged{"addUnmanaged"};
 constexpr shcore::Option_data<bool> kOptionRemoveObsolete{"removeObsolete"};
 constexpr shcore::Option_data<bool> kOptionUpgradeCommProtocol{
@@ -150,37 +137,12 @@ const shcore::Option_pack_def<Rescan_options> &Rescan_options::options() {
   static const auto opts = std::invoke([]() {
     shcore::Option_pack_builder<Rescan_options> b;
 
-    b.optional_as<shcore::Value>(
-        kOptionAddInstances, [](Rescan_options &options, std::string_view name,
-                                const shcore::Value &value) {
-          options.set_list_option(name, value);
-        });
-    b.optional_as<shcore::Value>(
-        kOptionRemoveInstances,
-        [](Rescan_options &options, std::string_view name,
-           const shcore::Value &value) {
-          options.set_list_option(name, value);
-        });
-
-    b.optional_as<bool>(kOptionAddUnmanaged, [](Rescan_options &options,
-                                                std::string_view, bool value) {
-      if (options.m_used_deprecated.has_value() &&
-          !options.m_used_deprecated.value())
-        throw_rescan_mixing_exception();
-
-      options.m_used_deprecated = true;
-      options.auto_add = value;
-    });
-    b.optional_as<bool>(
-        kOptionRemoveObsolete,
-        [](Rescan_options &options, std::string_view, bool value) {
-          if (options.m_used_deprecated.has_value() &&
-              !options.m_used_deprecated.value())
-            throw_rescan_mixing_exception();
-
-          options.m_used_deprecated = true;
-          options.auto_remove = value;
-        });
+    b.optional_as<bool>(kOptionAddUnmanaged,
+                        [](Rescan_options &options, std::string_view,
+                           bool value) { options.auto_add = value; });
+    b.optional_as<bool>(kOptionRemoveObsolete,
+                        [](Rescan_options &options, std::string_view,
+                           bool value) { options.auto_remove = value; });
 
     b.optional(kOptionUpgradeCommProtocol,
                &Rescan_options::upgrade_comm_protocol);
@@ -194,77 +156,6 @@ const shcore::Option_pack_def<Rescan_options> &Rescan_options::options() {
   return opts;
 }
 
-void Rescan_options::set_list_option(std::string_view name,
-                                     const shcore::Value &value) {
-  if (m_used_deprecated.has_value() && m_used_deprecated.value())
-    throw_rescan_mixing_exception();
-
-  m_used_deprecated = false;
-
-  auto console = mysqlsh::current_console();
-  console->print_info(shcore::str_format(
-      "The '%s' and '%s' options are deprecated. Please use '%s' and/or '%s' "
-      "instead.",
-      kAddInstances, kRemoveInstances, kAddUnmanaged, kRemoveObsolete));
-  console->print_info();
-
-  std::vector<mysqlshdk::db::Connection_options> *instances_list;
-
-  // Selects the target list
-  instances_list = (name == kOptionRemoveInstances.name)
-                       ? &remove_instances_list
-                       : &add_instances_list;
-
-  bool *auto_option =
-      (name == kOptionRemoveInstances.name) ? &auto_remove : &auto_add;
-
-  if (value.get_type() == shcore::Value_type::String) {
-    auto str_val = value.as_string();
-    if (shcore::str_caseeq(str_val, "auto")) {
-      *auto_option = true;
-    } else {
-      throw shcore::Exception::argument_error(shcore::str_format(
-          "Option '%.*s' only accepts 'auto' as a valid string "
-          "value, otherwise a list of instances is expected.",
-          static_cast<int>(name.length()), name.data()));
-    }
-  } else if (value.get_type() == shcore::Value_type::Array) {
-    auto instances_array = value.as_array();
-    if (instances_array->empty()) {
-      throw shcore::Exception::argument_error(
-          shcore::str_format("The list for '%.*s' option cannot be empty.",
-                             static_cast<int>(name.length()), name.data()));
-    }
-
-    // Process values from addInstances list (must be valid connection data).
-    for (const shcore::Value &instance : *instances_array.get()) {
-      try {
-        auto cnx_opt = mysqlsh::get_connection_options(instance);
-
-        if (cnx_opt.get_host().empty()) {
-          throw shcore::Exception::argument_error("host cannot be empty.");
-        }
-
-        if (!cnx_opt.has_port()) {
-          throw shcore::Exception::argument_error("port is missing.");
-        }
-
-        instances_list->emplace_back(std::move(cnx_opt));
-      } catch (const std::exception &err) {
-        std::string error(err.what());
-        throw shcore::Exception::argument_error(shcore::str_format(
-            "Invalid value '%s' for '%.*s' option: %s",
-            instance.descr().c_str(), static_cast<int>(name.length()),
-            name.data(), error.c_str()));
-      }
-    }
-  } else {
-    throw shcore::Exception::argument_error(shcore::str_format(
-        "The '%.*s' option must be a string or a list of strings.",
-        static_cast<int>(name.length()), name.data()));
-  }
-}
-
 namespace {
 constexpr shcore::Option_data<std::optional<uint32_t>>
     kOptionRunningTransactionsTimeout{"runningTransactionsTimeout"};
diff --git a/modules/adminapi/cluster/api_options.h b/modules/adminapi/cluster/api_options.h
index 5c9e37c71b..760d5c5965 100644
--- a/modules/adminapi/cluster/api_options.h
+++ b/modules/adminapi/cluster/api_options.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -86,18 +86,11 @@ struct Options_options {
 struct Rescan_options {
   static const shcore::Option_pack_def<Rescan_options> &options();
 
-  std::vector<mysqlshdk::db::Connection_options> add_instances_list;
-  std::vector<mysqlshdk::db::Connection_options> remove_instances_list;
   bool auto_add{false};
   bool auto_remove{false};
   bool upgrade_comm_protocol{false};
   std::optional<bool> update_view_change_uuid;
   std::optional<bool> repair_metadata;
-
- private:
-  void set_list_option(std::string_view name, const shcore::Value &value);
-
-  std::optional<bool> m_used_deprecated;
 };
 
 struct Set_primary_instance_options {
diff --git a/modules/adminapi/cluster/cluster_impl.cc b/modules/adminapi/cluster/cluster_impl.cc
index ffbba4b843..63ccb414b4 100644
--- a/modules/adminapi/cluster/cluster_impl.cc
+++ b/modules/adminapi/cluster/cluster_impl.cc
@@ -4362,13 +4362,6 @@ mysqlshdk::mysql::Lock_scoped Cluster_impl::get_lock(
   return mysqlshdk::mysql::Lock_scoped{std::move(release_cb)};
 }
 
-shcore::Dictionary_t Cluster_impl::routing_options(const std::string &router) {
-  auto dict = Base_cluster_impl::routing_options(router);
-  if (router.empty()) (*dict)["clusterName"] = shcore::Value(get_name());
-
-  return dict;
-}
-
 shcore::Dictionary_t Cluster_impl::router_options(
     const shcore::Option_pack_ref<Router_options_options> &options) {
   if (is_cluster_set_member()) {
diff --git a/modules/adminapi/cluster/cluster_impl.h b/modules/adminapi/cluster/cluster_impl.h
index b85e12a2b9..618684cd91 100644
--- a/modules/adminapi/cluster/cluster_impl.h
+++ b/modules/adminapi/cluster/cluster_impl.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2016, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -130,7 +130,6 @@ class Cluster_impl final : public Base_cluster_impl,
   shcore::Value status(int64_t extended);
   shcore::Value list_routers(bool only_upgrade_required) override;
 
-  shcore::Dictionary_t routing_options(const std::string &router) override;
   shcore::Dictionary_t router_options(
       const shcore::Option_pack_ref<Router_options_options> &options) override;
   void set_routing_option(const std::string &option,
diff --git a/modules/adminapi/cluster/rescan.cc b/modules/adminapi/cluster/rescan.cc
index 789ce1a591..8cecd31841 100644
--- a/modules/adminapi/cluster/rescan.cc
+++ b/modules/adminapi/cluster/rescan.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -54,57 +54,6 @@ Rescan::Rescan(Rescan_options options, Cluster_impl *cluster)
       m_cluster(cluster),
       m_repl_account_mng{*cluster} {
   assert(cluster);
-  assert((m_options.auto_add && m_options.add_instances_list.empty()) ||
-         !m_options.auto_add);
-  assert((m_options.auto_remove && m_options.remove_instances_list.empty()) ||
-         !m_options.auto_remove);
-}
-
-void Rescan::validate_list_duplicates() const {
-  std::set<std::string> add_instances_set, remove_instances_set;
-
-  // Lambda function to check for instance duplicates in list, creating a set
-  // with the instance address to compare later.
-  auto duplicates_check =
-      [](const std::string &option_name,
-         const std::vector<mysqlshdk::db::Connection_options> &in_list,
-         std::set<std::string> *out_set) {
-        for (const auto &cnx_opt : in_list) {
-          std::string instance_address =
-              cnx_opt.as_uri(mysqlshdk::db::uri::formats::only_transport());
-          auto res = out_set->insert(instance_address);
-          if (!res.second) {
-            throw shcore::Exception::argument_error(
-                "Duplicated value found for instance '" + instance_address +
-                "' in '" + option_name + "' option.");
-          }
-        }
-      };
-
-  // Check for duplicates in the addInstances list.
-  duplicates_check("addInstances", m_options.add_instances_list,
-                   &add_instances_set);
-
-  // Check for duplicates in the removeInstances list.
-  duplicates_check("removeInstances", m_options.remove_instances_list,
-                   &remove_instances_set);
-
-  // Find common instance in the two options.
-  std::vector<std::string> repeated_instances;
-  std::set_intersection(add_instances_set.begin(), add_instances_set.end(),
-                        remove_instances_set.begin(),
-                        remove_instances_set.end(),
-                        std::back_inserter(repeated_instances));
-
-  // The same instance cannot be included in both options.
-  if (!repeated_instances.empty()) {
-    std::string plural = (repeated_instances.size() > 1) ? "s" : "";
-    throw shcore::Exception::argument_error(
-        "The same instance" + plural +
-        " cannot be used in both 'addInstances' and 'removeInstances' options: "
-        "'" +
-        shcore::str_join(repeated_instances, ", ") + "'.");
-  }
 }
 
 void Rescan::ensure_unavailable_instances_not_auto_rejoining(
@@ -162,35 +111,11 @@ std::vector<std::string> Rescan::detect_invalid_members(
 }
 
 void Rescan::prepare() {
-  // Validate duplicates in addInstances and removeInstances options.
-  validate_list_duplicates();
-
   auto console = mysqlsh::current_console();
 
-  // All instance to add must be an active member of the GR group.
-  {
-    std::vector<std::string> invalid_add_members =
-        detect_invalid_members(m_options.add_instances_list, true);
-
-    if (!invalid_add_members.empty()) {
-      console->print_error(
-          "The following instances cannot be added because they are not active "
-          "members of the cluster: '" +
-          shcore::str_join(invalid_add_members, ", ") +
-          "'. Please verify if the specified addresses are correct, or if the "
-          "instances are currently inactive.");
-      console->print_info();
-
-      throw shcore::Exception::runtime_error(
-          "The following instances are not active members of the cluster: '" +
-          shcore::str_join(invalid_add_members, ", ") + "'.");
-    }
-  }
-
   // can't add instances if recovery auth isn't password
-  if (((!m_options.add_instances_list.empty() || m_options.auto_add) &&
-       (m_cluster->query_cluster_auth_type() !=
-        Replication_auth_type::PASSWORD))) {
+  if (m_options.auto_add && (m_cluster->query_cluster_auth_type() !=
+                             Replication_auth_type::PASSWORD)) {
     console->print_error(
         "Unrecognized members were detected in the group, but the cluster is "
         "configured to require SSL certificate authentication. Please stop GR "
@@ -205,23 +130,6 @@ void Rescan::prepare() {
         to_string(m_cluster->query_cluster_auth_type()).c_str()));
   }
 
-  // All instance to remove cannot be an active member of the GR group.
-  std::vector<std::string> invalid_remove_members =
-      detect_invalid_members(m_options.remove_instances_list, false);
-
-  if (!invalid_remove_members.empty()) {
-    console->print_error(
-        "The following instances cannot be removed because they are active "
-        "members of the cluster: '" +
-        shcore::str_join(invalid_remove_members, ", ") +
-        "'. Please verify if the specified addresses are correct.");
-    console->print_info();
-
-    throw shcore::Exception::runtime_error(
-        "The following instances are active members of the cluster: '" +
-        shcore::str_join(invalid_remove_members, ", ") + "'.");
-  }
-
   // Check if group_replication_view_change_uuid is supported
   m_is_view_change_uuid_supported = m_cluster->get_lowest_instance_version() >=
                                     Precondition_checker::k_min_cs_version;
@@ -632,26 +540,6 @@ void Rescan::remove_instance_from_metadata(
   console->print_info();
 }
 
-namespace {
-using Instance_list = std::vector<mysqlshdk::db::Connection_options>;
-Instance_list::const_iterator find_in_instance_list(
-    const Instance_list &list, std::string_view instance_address) {
-  if (list.empty()) return list.end();
-
-  // Lambda function (predicate) to check if the instance address
-  // matches.
-  auto in_list =
-      [&instance_address](const mysqlshdk::db::Connection_options &cnx_opt) {
-        std::string address =
-            cnx_opt.as_uri(mysqlshdk::db::uri::formats::only_transport());
-        return mysqlshdk::utils::are_endpoints_equal(address, instance_address);
-      };
-
-  // Return iterator with a matching address (if in the list).
-  return std::find_if(list.begin(), list.end(), in_list);
-}
-}  // namespace
-
 void Rescan::add_metadata_for_instances(
     const std::shared_ptr<shcore::Value::Array_type> &instance_list) {
   if (instance_list->empty()) return;
@@ -675,16 +563,9 @@ void Rescan::add_metadata_for_instances(
     console->print_info("A new instance '" + instance_address +
                         "' was discovered in the cluster.");
 
-    // Check if the new instance belongs to the addInstances list.
-    auto list_iter =
-        find_in_instance_list(m_options.add_instances_list, instance_address);
-
     // Decide to add/remove instance based on given operation options.
     mysqlshdk::db::Connection_options instance_cnx_opts;
-    if (list_iter != m_options.add_instances_list.end()) {
-      instance_cnx_opts = *list_iter;
-      m_options.add_instances_list.erase(list_iter);
-    } else if (m_options.auto_add) {
+    if (m_options.auto_add) {
       instance_cnx_opts = mysqlshdk::db::Connection_options(instance_address);
     } else if (current_shell_options()->get().wizards) {
       if (console->confirm("Would you like to add it to the cluster metadata?",
@@ -711,15 +592,7 @@ void Rescan::remove_metadata_for_instances(
     console->print_info("The instance '" + instance_address +
                         "' is no longer part of the cluster.");
 
-    // Check if the new instance belongs to the addInstances list.
-    // Decide to add/remove instance based on given operation options.
-    auto list_iter = find_in_instance_list(m_options.remove_instances_list,
-                                           instance_address);
-    if (list_iter != m_options.remove_instances_list.end()) {
-      remove_instance_from_metadata(instance_address);
-
-      m_options.remove_instances_list.erase(list_iter);
-    } else if (m_options.auto_remove) {
+    if (m_options.auto_remove) {
       // Remove instance automatically if "auto" was used.
       remove_instance_from_metadata(instance_address);
     } else if (current_shell_options()->get().wizards) {
@@ -1115,26 +988,6 @@ shcore::Value Rescan::execute() {
     add_metadata_for_instances(result->get_array("newlyDiscoveredInstances"));
   }
 
-  // Print warning about not used instances in addInstances.
-  if (!m_options.add_instances_list.empty()) {
-    std::vector<std::string> not_used_add_instances;
-    not_used_add_instances.reserve(m_options.add_instances_list.size());
-
-    for (const auto &cnx_opts : m_options.add_instances_list) {
-      not_used_add_instances.push_back(
-          cnx_opts.as_uri(mysqlshdk::db::uri::formats::only_transport()));
-    }
-
-    if (!not_used_add_instances.empty()) {
-      console->print_warning(
-          "The following instances were not added to the metadata because they "
-          "are already part of the cluster: '" +
-          shcore::str_join(not_used_add_instances, ", ") +
-          "'. Please verify if the specified value for 'addInstances' option "
-          "is correct.");
-    }
-  }
-
   // Check if there are missing instances
   if (result->has_key("unavailableInstances")) {
     remove_metadata_for_instances(result->get_array("unavailableInstances"));
@@ -1206,26 +1059,6 @@ shcore::Value Rescan::execute() {
   // in all cluster members
   ensure_transaction_size_limit_consistency();
 
-  // Print warning about not used instances in removeInstances.
-  if (!m_options.remove_instances_list.empty()) {
-    std::vector<std::string> not_used_remove_instances;
-    not_used_remove_instances.reserve(m_options.remove_instances_list.size());
-
-    for (const auto &cnx_opts : m_options.remove_instances_list) {
-      not_used_remove_instances.push_back(
-          cnx_opts.as_uri(mysqlshdk::db::uri::formats::only_transport()));
-    }
-    if (!not_used_remove_instances.empty()) {
-      console->print_warning(
-          "The following instances were not removed from the metadata because "
-          "they are already not part of the cluster or are running auto-rejoin"
-          ": '" +
-          shcore::str_join(not_used_remove_instances, ", ") +
-          "'. Please verify if the specified value for 'removeInstances' "
-          "option is correct.");
-    }
-  }
-
   // Check if the topology mode changed.
   // NOTE: This must be performed after updating the instances in the metadata,
   //       in order for the list of instance to be retrieved properly to update
diff --git a/modules/adminapi/cluster/rescan.h b/modules/adminapi/cluster/rescan.h
index 27a36e9d9b..df07e1466d 100644
--- a/modules/adminapi/cluster/rescan.h
+++ b/modules/adminapi/cluster/rescan.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -93,12 +93,6 @@ class Rescan : public Command_interface {
   Replication_account m_repl_account_mng;
   std::vector<Cluster_metadata> m_invalid_clusterset_clusters;
 
-  /**
-   * Validate existence of duplicates for the addInstances and removeInstances
-   * options.
-   */
-  void validate_list_duplicates() const;
-
   /**
    * Goes through the list of unavailable instances, and checks if they are
    * running auto-rejoin. If they are print a warning to let the user know and
diff --git a/modules/adminapi/cluster_set/cluster_set_impl.cc b/modules/adminapi/cluster_set/cluster_set_impl.cc
index 6970e8b9e9..5de8dd7055 100644
--- a/modules/adminapi/cluster_set/cluster_set_impl.cc
+++ b/modules/adminapi/cluster_set/cluster_set_impl.cc
@@ -820,6 +820,10 @@ void Cluster_set_impl::ensure_compatible_clone_donor(
   Base_cluster_impl::check_compatible_clone_donor(donor, recipient);
 }
 
+std::vector<Router_metadata> Cluster_set_impl::get_routers() const {
+  return get_metadata_storage()->get_clusterset_routers(get_id());
+}
+
 shcore::Value Cluster_set_impl::create_replica_cluster(
     const std::string &instance_def, const std::string &cluster_name,
     Recovery_progress_style progress_style,
@@ -1830,14 +1834,6 @@ shcore::Value Cluster_set_impl::list_routers(const std::string &router) {
   return routers;
 }
 
-shcore::Dictionary_t Cluster_set_impl::routing_options(
-    const std::string &router) {
-  auto dict = Base_cluster_impl::routing_options(router);
-  if (router.empty()) (*dict)["domainName"] = shcore::Value(get_name());
-
-  return dict;
-}
-
 void Cluster_set_impl::delete_async_channel(Cluster_impl *cluster,
                                             bool dry_run) {
   cluster->execute_in_members(
diff --git a/modules/adminapi/cluster_set/cluster_set_impl.h b/modules/adminapi/cluster_set/cluster_set_impl.h
index e8f3d4451f..14e0ebce35 100644
--- a/modules/adminapi/cluster_set/cluster_set_impl.h
+++ b/modules/adminapi/cluster_set/cluster_set_impl.h
@@ -186,6 +186,8 @@ class Cluster_set_impl : public Base_cluster_impl,
       const mysqlshdk::mysql::IInstance &donor,
       const mysqlshdk::mysql::IInstance &recipient) const;
 
+  std::vector<Router_metadata> get_routers() const override;
+
  protected:
   void _set_option(const std::string &option,
                    const shcore::Value &value) override;
@@ -235,7 +237,6 @@ class Cluster_set_impl : public Base_cluster_impl,
   void ensure_replica_settings(Cluster_impl *replica, bool dry_run);
 
   shcore::Value list_routers(const std::string &router);
-  shcore::Dictionary_t routing_options(const std::string &router) override;
 
   void record_in_metadata(const Cluster_id &seed_cluster_id,
                           const clusterset::Create_cluster_set_options &options,
diff --git a/modules/adminapi/common/api_options.cc b/modules/adminapi/common/api_options.cc
index 907500ac4a..19b3cca757 100644
--- a/modules/adminapi/common/api_options.cc
+++ b/modules/adminapi/common/api_options.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -216,9 +216,31 @@ const shcore::Option_pack_def<Create_routing_guideline_options>
 
 const shcore::Option_pack_def<Import_routing_guideline_options>
     &Import_routing_guideline_options::options() {
-  static const auto opts =
-      shcore::Option_pack_def<Import_routing_guideline_options>()
-          .include<Force_options>();
+  static const auto opts = std::invoke([]() {
+    shcore::Option_pack_builder<Import_routing_guideline_options> b;
+
+    b.include<Force_options>();
+
+    b.optional_as<std::string>(
+        kOptionRename, [](Import_routing_guideline_options &options,
+                          std::string_view, const std::string &value) {
+          if (options.force.has_value()) {
+            throw shcore::Exception::argument_error(shcore::str_format(
+                "Options '%s' and '%s' are mutually exclusive.", kForce,
+                std::string(kOptionRename.name).c_str()));
+          }
+
+          if (value.empty()) {
+            throw shcore::Exception::argument_error(
+                shcore::str_format("Option '%s' cannot be empty.",
+                                   std::string(kOptionRename.name).c_str()));
+          }
+
+          options.rename = value;
+        });
+
+    return b.build();
+  });
 
   return opts;
 }
diff --git a/modules/adminapi/common/api_options.h b/modules/adminapi/common/api_options.h
index b60e8d2235..d41fff2c11 100644
--- a/modules/adminapi/common/api_options.h
+++ b/modules/adminapi/common/api_options.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -117,6 +117,8 @@ struct Create_routing_guideline_options : public Force_options {
 struct Import_routing_guideline_options : public Force_options {
   static const shcore::Option_pack_def<Import_routing_guideline_options>
       &options();
+
+  std::optional<std::string> rename;
 };
 
 }  // namespace dba
diff --git a/modules/adminapi/common/base_cluster_impl.cc b/modules/adminapi/common/base_cluster_impl.cc
index 11032ea4c1..c0d7f0af50 100644
--- a/modules/adminapi/common/base_cluster_impl.cc
+++ b/modules/adminapi/common/base_cluster_impl.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2016, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -73,32 +73,6 @@ namespace {
 const built_in_tags_map_t k_supported_set_option_tags{
     {"_hidden", shcore::Value_type::Bool},
     {"_disconnect_existing_sessions_when_hidden", shcore::Value_type::Bool}};
-
-Command_conditions::Builder gen_routing_options_builder(Cluster_type type,
-                                                        std::string_view name) {
-  switch (type) {
-    case Cluster_type::GROUP_REPLICATION:
-      return Command_conditions::Builder::gen_cluster(name)
-          .target_instance(TargetType::InnoDBCluster,
-                           TargetType::InnoDBClusterSet)
-          .primary_required()
-          .allowed_on_fence();
-    case Cluster_type::ASYNC_REPLICATION:
-      return Command_conditions::Builder::gen_replicaset(name)
-          .primary_required();
-
-    case Cluster_type::REPLICATED_CLUSTER:
-      return Command_conditions::Builder::gen_clusterset(name)
-          .primary_not_required()
-          .allowed_on_fence();
-
-    default:
-      throw std::logic_error(
-          shcore::str_format("Command context is incorrect for \"%.*s\".",
-                             static_cast<int>(name.size()), name.data()));
-  }
-}
-
 }  // namespace
 
 Base_cluster_impl::Base_cluster_impl(
@@ -1575,35 +1549,42 @@ void Base_cluster_impl::set_routing_option(const std::string &router,
   }
 }
 
-shcore::Dictionary_t Base_cluster_impl::routing_options(
-    const std::string &router) {
-  {
-    auto type = get_type();
-
-    auto b = gen_routing_options_builder(type, "routingOptions");
-    if (type == Cluster_type::GROUP_REPLICATION)
-      b.compatibility_check(
-          metadata::Compatibility::INCOMPATIBLE_MIN_VERSION_2_0_0);
-
-    check_preconditions(b.build());
-  }
-
-  return mysqlsh::dba::routing_options(get_metadata_storage().get(), get_type(),
-                                       get_id(), router);
-}
-
 shcore::Dictionary_t Base_cluster_impl::router_options(
     const shcore::Option_pack_ref<Router_options_options> &options) {
   {
-    auto type = get_type();
+    Command_conditions conds;
+    switch (get_type()) {
+      case Cluster_type::GROUP_REPLICATION:
+        conds = Command_conditions::Builder::gen_cluster("routerOptions")
+                    .target_instance(TargetType::InnoDBCluster,
+                                     TargetType::InnoDBClusterSet)
+                    .primary_required()
+                    .allowed_on_fence()
+                    .compatibility_check(
+                        metadata::Compatibility::INCOMPATIBLE_MIN_VERSION_2_1_0)
+                    .build();
+        break;
+      case Cluster_type::ASYNC_REPLICATION:
+        conds = Command_conditions::Builder::gen_replicaset("routerOptions")
+                    .primary_required()
+                    .compatibility_check(
+                        metadata::Compatibility::INCOMPATIBLE_MIN_VERSION_2_1_0)
+                    .build();
+        break;
 
-    auto b = gen_routing_options_builder(type, "routerOptions");
-    if ((type == Cluster_type::GROUP_REPLICATION) ||
-        (type == Cluster_type::ASYNC_REPLICATION))
-      b.compatibility_check(
-          metadata::Compatibility::INCOMPATIBLE_MIN_VERSION_2_1_0);
+      case Cluster_type::REPLICATED_CLUSTER:
+        conds = Command_conditions::Builder::gen_clusterset("routerOptions")
+                    .primary_not_required()
+                    .allowed_on_fence()
+                    .build();
+        break;
+
+      default:
+        throw std::logic_error(shcore::str_format(
+            "Command context is incorrect for \"routerOptions\"."));
+    }
 
-    check_preconditions(b.build());
+    check_preconditions(conds);
   }
 
   return mysqlsh::dba::router_options(get_metadata_storage().get(), get_type(),
@@ -2078,4 +2059,8 @@ Base_cluster_impl::import_routing_guideline(
   return Routing_guideline_impl::import(self, file_path, options);
 }
 
+std::vector<Router_metadata> Base_cluster_impl::get_routers() const {
+  return get_metadata_storage()->get_routers(get_id());
+}
+
 }  // namespace mysqlsh::dba
diff --git a/modules/adminapi/common/base_cluster_impl.h b/modules/adminapi/common/base_cluster_impl.h
index 4538f52c20..2eb23d1491 100644
--- a/modules/adminapi/common/base_cluster_impl.h
+++ b/modules/adminapi/common/base_cluster_impl.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2016, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -201,7 +201,6 @@ class Base_cluster_impl {
   virtual void set_routing_option(const std::string &router,
                                   const std::string &option,
                                   const shcore::Value &value);
-  virtual shcore::Dictionary_t routing_options(const std::string &router);
 
   virtual shcore::Dictionary_t router_options(
       const shcore::Option_pack_ref<Router_options_options> &options);
@@ -253,6 +252,8 @@ class Base_cluster_impl {
       std::shared_ptr<Base_cluster_impl> self, const std::string &file_path,
       const shcore::Option_pack_ref<Import_routing_guideline_options> &options);
 
+  virtual std::vector<Router_metadata> get_routers() const;
+
  public:
   /*
    * Synchronize transactions on target instance.
diff --git a/modules/adminapi/common/common.h b/modules/adminapi/common/common.h
index d7f7d97ed7..984e1698af 100644
--- a/modules/adminapi/common/common.h
+++ b/modules/adminapi/common/common.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2016, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -234,8 +234,6 @@ inline constexpr const char kMultiPrimary[] = "multiPrimary";
 inline constexpr const char kForce[] = "force";
 inline constexpr const char kDissolve[] = "dissolve";
 inline constexpr const char kAdoptFromGR[] = "adoptFromGR";
-inline constexpr const char kAddInstances[] = "addInstances";
-inline constexpr const char kRemoveInstances[] = "removeInstances";
 inline constexpr const char kAddUnmanaged[] = "addUnmanaged";
 inline constexpr const char kRemoveObsolete[] = "removeObsolete";
 inline constexpr const char kRejoinInstances[] = "rejoinInstances";
diff --git a/modules/adminapi/common/common_cmd_options.h b/modules/adminapi/common/common_cmd_options.h
index 7cb8b3904d..197ba40dc6 100644
--- a/modules/adminapi/common/common_cmd_options.h
+++ b/modules/adminapi/common/common_cmd_options.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2024, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -172,6 +172,8 @@ inline constexpr shcore::Option_data<bool> kOptionGtidSetIsComplete{
 inline constexpr shcore::Option_data<std::string> kOptionReplicationAllowedHost{
     "replicationAllowedHost"};
 
+inline constexpr shcore::Option_data<std::string> kOptionRename{"rename"};
+
 }  // namespace mysqlsh::dba
 
 #endif  // MODULES_ADMINAPI_COMMON_COMMON_CMD_OPTIONS_H_
diff --git a/modules/adminapi/common/metadata_storage.cc b/modules/adminapi/common/metadata_storage.cc
index 2987ebbcdc..10203a990b 100644
--- a/modules/adminapi/common/metadata_storage.cc
+++ b/modules/adminapi/common/metadata_storage.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2016, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -2698,134 +2698,6 @@ std::vector<Router_metadata> MetadataStorage::get_clusterset_routers(
   return ret_val;
 }
 
-namespace {
-template <class TOptions>
-std::string router_opt_select_items(std::string_view prefix,
-                                    const TOptions &options) {
-  std::string ret;
-  for (const auto &opt : options) {
-    if (opt == k_router_option_target_cluster) {
-      ret += ", JSON_QUOTE(IF(";
-      ret.append(prefix);
-      ret +=
-          "->>'$.target_cluster'='primary', 'primary', "
-          "(SELECT cluster_name FROM mysql_innodb_cluster_metadata.clusters "
-          "c "
-          "WHERE c.attributes->>'$.group_replication_group_name' = ";
-      ret.append(prefix);
-      ret += "->>'$.target_cluster' limit 1))) AS target_cluster";
-    } else if (opt == k_router_option_routing_guideline) {
-      ret += ", ";
-      ret +=
-          "JSON_QUOTE((SELECT name FROM "
-          "mysql_innodb_cluster_metadata.routing_guidelines "
-          "WHERE guideline_id = ";
-      ret.append(prefix);
-      ret += "->'$.";
-      ret += opt;
-      ret += "' limit 1)) AS ";
-      ret += opt;
-    } else {
-      ret += ", ";
-      ret.append(prefix);
-      ret += "->'$.";
-      ret += opt;
-      ret += "' as ";
-      ret += opt;
-    }
-  }
-  return ret + " ";
-}
-
-Routing_options_metadata fetch_router_options(
-    const std::shared_ptr<mysqlshdk::db::IResult> &result,
-    const std::map<std::string, shcore::Value> &option_defaults) {
-  Routing_options_metadata ret;
-
-  while (auto row = result->fetch_one_named()) {
-    std::map<std::string, shcore::Value> rom;
-    bool is_global = row.is_null("router_label");
-
-    for (const auto &option : option_defaults) {
-      if (!row.is_null(option.first)) {
-        rom.emplace(option.first,
-                    shcore::Value::parse(row.get_string(option.first)));
-      } else {
-        // fill-in default values for global options
-        if (is_global) rom.emplace(option.first, option.second);
-      }
-    }
-    if (!is_global) {
-      ret.routers[row.get_string("router_label")] = std::move(rom);
-    } else {
-      ret.global = std::move(rom);
-    }
-  }
-  return ret;
-}
-}  // namespace
-
-Routing_options_metadata MetadataStorage::get_routing_options(
-    Cluster_type type, const std::string &id) {
-  std::string query;
-  std::map<std::string, shcore::Value> option_defaults;
-
-  switch (type) {
-    case Cluster_type::REPLICATED_CLUSTER:
-      option_defaults = {k_default_clusterset_router_options.begin(),
-                         k_default_clusterset_router_options.end()};
-
-      if (installed_version() < mysqlshdk::utils::Version(2, 2)) {
-        assert(option_defaults.find(k_router_option_stats_updates_frequency) !=
-               option_defaults.end());
-        option_defaults[k_router_option_stats_updates_frequency] =
-            shcore::Value(0);
-      }
-
-      query =
-          "SELECT concat(r.address, '::', r.router_name) AS router_label" +
-          router_opt_select_items("r.options", k_clusterset_router_options) +
-          "FROM mysql_innodb_cluster_metadata.routers AS r UNION SELECT "
-          "NULL" +
-          router_opt_select_items("cs.router_options",
-                                  k_clusterset_router_options) +
-          "FROM mysql_innodb_cluster_metadata.clustersets AS cs WHERE "
-          "clusterset_id = ?";
-      break;
-    case Cluster_type::GROUP_REPLICATION:
-      option_defaults = {k_default_cluster_router_options.begin(),
-                         k_default_cluster_router_options.end()};
-
-      query = "SELECT concat(r.address, '::', r.router_name) AS router_label" +
-              router_opt_select_items("r.options", k_cluster_router_options) +
-              "FROM mysql_innodb_cluster_metadata.routers AS r UNION SELECT "
-              "NULL" +
-              router_opt_select_items("c.router_options",
-                                      k_cluster_router_options) +
-              "FROM mysql_innodb_cluster_metadata.clusters AS c WHERE "
-              "cluster_id = ?";
-      break;
-    case Cluster_type::ASYNC_REPLICATION:
-      option_defaults = {k_default_replicaset_router_options.begin(),
-                         k_default_replicaset_router_options.end()};
-
-      query =
-          "SELECT concat(r.address, '::', r.router_name) AS router_label" +
-          router_opt_select_items("r.options", k_replicaset_router_options) +
-          "FROM mysql_innodb_cluster_metadata.routers AS r UNION SELECT "
-          "NULL" +
-          router_opt_select_items("c.router_options",
-                                  k_replicaset_router_options) +
-          "FROM mysql_innodb_cluster_metadata.clusters AS c WHERE "
-          "cluster_id = ?";
-      break;
-    case Cluster_type::NONE:
-      throw std::logic_error("internal error");
-  }
-
-  return fetch_router_options(execute_sqlf(query, id), option_defaults);
-}
-
 shcore::Value MetadataStorage::get_router_options(Cluster_type type,
                                                   const std::string &id,
                                                   const std::string &router) {
diff --git a/modules/adminapi/common/metadata_storage.h b/modules/adminapi/common/metadata_storage.h
index aa52e6c505..595d4cb5db 100644
--- a/modules/adminapi/common/metadata_storage.h
+++ b/modules/adminapi/common/metadata_storage.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2016, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -149,11 +149,6 @@ struct Router_metadata {
   shcore::Dictionary_t tags = nullptr;
 };
 
-struct Routing_options_metadata {
-  std::map<std::string, shcore::Value> global;
-  std::map<std::string, std::map<std::string, shcore::Value>> routers;
-};
-
 struct Routing_guideline_metadata {
   std::string id;
   std::string name;
@@ -485,9 +480,6 @@ class MetadataStorage {
   std::vector<Router_metadata> get_routers(const Cluster_id &cluster_id);
   std::vector<Router_metadata> get_clusterset_routers(const Cluster_set_id &cs);
 
-  Routing_options_metadata get_routing_options(Cluster_type type,
-                                               const std::string &id);
-
   shcore::Value get_router_options(Cluster_type type, const std::string &id,
                                    const std::string &router_name = "");
 
diff --git a/modules/adminapi/common/router.cc b/modules/adminapi/common/router.cc
index 403c2d8666..ad47cb8735 100644
--- a/modules/adminapi/common/router.cc
+++ b/modules/adminapi/common/router.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -277,59 +277,6 @@ shcore::Value clusterset_list_routers(MetadataStorage *md,
   return shcore::Value(std::move(router_list));
 }
 
-shcore::Dictionary_t routing_options(MetadataStorage *md, Cluster_type type,
-                                     const std::string &id,
-                                     const std::string &router_label) {
-  auto router_options = shcore::make_dict();
-  auto romd = md->get_routing_options(type, id);
-
-  if (Cluster_set_id cs_id;
-      (type == Cluster_type::GROUP_REPLICATION) &&
-      md->check_cluster_set(nullptr, nullptr, nullptr, &cs_id)) {
-    // if the Cluster belongs to a ClusterSet, we need to replace the options
-    // in the Cluster with the options in the ClusterSet
-    auto cs_romd =
-        md->get_routing_options(Cluster_type::REPLICATED_CLUSTER, cs_id);
-
-    for (const auto &[option, value] : cs_romd.global) {
-      if (option == "tags") continue;
-
-      auto it = romd.global.find(option);
-      if (it != romd.global.end()) it->second = value;
-    }
-  }
-
-  const auto get_options_dict =
-      [](const std::map<std::string, shcore::Value> &entry) {
-        auto ret = shcore::make_dict();
-        for (const auto &option : entry) (*ret)[option.first] = option.second;
-
-        return shcore::Value(std::move(ret));
-      };
-
-  if (!router_label.empty()) {
-    if (romd.routers.find(router_label) != romd.routers.end()) {
-      const auto &entry = romd.routers[router_label];
-
-      (*router_options)[router_label] = get_options_dict(entry);
-    } else {
-      throw shcore::Exception::argument_error(
-          "Router '" + router_label + "' is not registered in the " +
-          to_display_string(type, Display_form::THING));
-    }
-  } else {
-    auto routers = shcore::make_dict();
-
-    (*router_options)["global"] = get_options_dict(romd.global);
-    for (const auto &entry : romd.routers) {
-      (*routers)[entry.first] = get_options_dict(entry.second);
-    }
-    (*router_options)["routers"] = shcore::Value(routers);
-  }
-
-  return router_options;
-}
-
 shcore::Dictionary_t router_options(MetadataStorage *md, Cluster_type type,
                                     const std::string &id,
                                     const std::string &name,
diff --git a/modules/adminapi/common/router.h b/modules/adminapi/common/router.h
index 8c675d6718..93d47c9d32 100644
--- a/modules/adminapi/common/router.h
+++ b/modules/adminapi/common/router.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2019, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -121,10 +121,6 @@ shcore::Value clusterset_list_routers(MetadataStorage *md,
                                       const Cluster_set_id &clusterset_id,
                                       const std::string &router);
 
-shcore::Dictionary_t routing_options(MetadataStorage *md, Cluster_type type,
-                                     const std::string &id,
-                                     const std::string &router_label = "");
-
 shcore::Dictionary_t router_options(MetadataStorage *md, Cluster_type type,
                                     const std::string &id,
                                     const std::string &name,
diff --git a/modules/adminapi/common/routing_guideline_impl.cc b/modules/adminapi/common/routing_guideline_impl.cc
index 59b1686633..d09e5faef5 100644
--- a/modules/adminapi/common/routing_guideline_impl.cc
+++ b/modules/adminapi/common/routing_guideline_impl.cc
@@ -36,7 +36,6 @@
 #include <stdexcept>
 
 // from mysql sources
-#include "my_inttypes.h"
 #include "routing_guidelines/routing_guidelines.h"
 
 #include "modules/adminapi/cluster_set/cluster_set_impl.h"
@@ -50,7 +49,6 @@
 #include "mysqlshdk/libs/utils/version.h"
 #include "shellcore/console.h"
 #include "utils/document_parser.h"
-#include "utils/error.h"
 #include "utils/utils_buffered_input.h"
 #include "utils/utils_file.h"
 #include "utils/utils_path.h"
@@ -100,14 +98,7 @@ mysqlshdk::utils::Version check_if_cluster_supports_guideline_version(
     Base_cluster_impl *owner, const mysqlshdk::utils::Version &version) {
   // check if we can use the given version considering what the
   // routers in the cluster support
-  std::vector<Router_metadata> routers;
-  auto md = owner->get_metadata_storage();
-
-  if (owner->get_type() == Cluster_type::REPLICATED_CLUSTER) {
-    routers = md->get_clusterset_routers(owner->get_id());
-  } else {
-    routers = md->get_routers(owner->get_id());
-  }
+  auto routers = owner->get_routers();
 
   // if there are no routers, then anything is ok
   if (routers.empty()) {
@@ -609,9 +600,7 @@ void Routing_guideline_impl::show(
   std::unique_ptr<Router_configuration_schema> router_options_parsed;
 
   if (options->router.has_value()) {
-    // Get router metadata
-    const auto &routers_md =
-        m_owner->get_metadata_storage()->get_routers(m_owner->get_id());
+    auto routers_md = m_owner->get_routers();
 
     auto it = std::find_if(
         routers_md.begin(), routers_md.end(), [&](const auto &router_md) {
@@ -1165,8 +1154,7 @@ Routing_guideline_impl::classify_topology(const std::string &router) const {
       };
 
   if (!router.empty()) {
-    const auto &routers_md =
-        m_owner->get_metadata_storage()->get_routers(m_owner->get_id());
+    auto routers_md = m_owner->get_routers();
 
     // Lookup for 'router'
     auto it = std::find_if(
@@ -1559,9 +1547,25 @@ std::shared_ptr<Routing_guideline_impl> Routing_guideline_impl::import(
   auto guideline_impl = std::make_shared<Routing_guideline_impl>(owner);
   guideline_impl->parse(shcore::Value::parse(parsed_document).as_map());
 
-  // Check if a Guideline with that name already exists. Skip if forced is
-  // used and keep the same it if needed
-  guideline_impl->ensure_unique_or_reuse(owner, options->get_force());
+  // Handle options: 'force' or 'rename' (mutually exclusive)
+  if (!options->rename.has_value()) {
+    // Check if a Guideline with that name already exists. Skip if 'force' is
+    // used and keep the same if needed
+    guideline_impl->ensure_unique_or_reuse(owner, options->get_force());
+  } else {
+    // Rename the guideline but first check if a guideline with the new name
+    // already exists
+    const auto &new_name = *options->rename;
+
+    if (owner->has_guideline(new_name)) {
+      throw shcore::Exception::argument_error(shcore::str_format(
+          "A Routing Guideline with the name '%s' already exists",
+          new_name.c_str()));
+    }
+
+    // Rename the parsed guideline
+    guideline_impl->set_name(new_name);
+  }
 
   // Store the guideline in the topology and print informative messages
   owner->store_routing_guideline(guideline_impl);
@@ -1847,8 +1851,7 @@ void Routing_guideline_impl::rename(const std::string &name) {
         name.c_str()));
   }
 
-  m_name = name;
-  m_guideline_doc->set("name", shcore::Value(name));
+  set_name(name);
 
   save_guideline();
 
diff --git a/modules/adminapi/common/routing_guideline_impl.h b/modules/adminapi/common/routing_guideline_impl.h
index 2bb2458dcb..963d7d4b67 100644
--- a/modules/adminapi/common/routing_guideline_impl.h
+++ b/modules/adminapi/common/routing_guideline_impl.h
@@ -71,6 +71,12 @@ class Routing_guideline_impl {
   std::shared_ptr<Base_cluster_impl> owner() const { return m_owner; }
 
   const std::string &get_name() const { return m_name; }
+
+  void set_name(const std::string &name) {
+    m_name = name;
+    m_guideline_doc->set("name", shcore::Value(name));
+  }
+
   const std::string &get_id() const { return m_id; }
   void set_id(const std::string &id) { m_id = id; }
   void set_as_default() { m_is_default_guideline = true; }
diff --git a/modules/adminapi/dba/api_options.cc b/modules/adminapi/dba/api_options.cc
index 3dbcd6968b..b0222dec63 100644
--- a/modules/adminapi/dba/api_options.cc
+++ b/modules/adminapi/dba/api_options.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -46,6 +46,7 @@ constexpr shcore::Option_data<std::string> kOptionAllowRootFrom{
 constexpr shcore::Option_data<bool> kOptionIgnoreSSLError{"ignoreSslError"};
 constexpr shcore::Option_data<shcore::Array_t> kOptionMysqldOptions{
     "mysqldOptions"};
+constexpr shcore::Option_data<std::string> kOptionMysqldPath{"mysqldPath"};
 }  // namespace
 
 const shcore::Option_pack_def<Common_sandbox_options>
@@ -101,6 +102,7 @@ const shcore::Option_pack_def<Deploy_sandbox_options>
     b.optional(kOptionAllowRootFrom, &Deploy_sandbox_options::allow_root_from);
     b.optional(kOptionIgnoreSSLError,
                &Deploy_sandbox_options::ignore_ssl_error);
+    b.optional(kOptionMysqldPath, &Deploy_sandbox_options::mysqld_path);
     b.optional(kOptionMysqldOptions, &Deploy_sandbox_options::mysqld_options);
 
     return b.build();
diff --git a/modules/adminapi/dba/api_options.h b/modules/adminapi/dba/api_options.h
index 56204497d0..abd00f65a4 100644
--- a/modules/adminapi/dba/api_options.h
+++ b/modules/adminapi/dba/api_options.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -64,6 +64,7 @@ struct Deploy_sandbox_options final : public Common_sandbox_options {
   std::optional<int> xport;
   std::string allow_root_from{"%"};
   bool ignore_ssl_error{false};
+  std::string mysqld_path;
   shcore::Array_t mysqld_options;
 };
 
diff --git a/modules/adminapi/mod_dba.cc b/modules/adminapi/mod_dba.cc
index f49818a861..9fb1298825 100644
--- a/modules/adminapi/mod_dba.cc
+++ b/modules/adminapi/mod_dba.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2016, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -2227,6 +2227,8 @@ affected by the provided options:
 pattern (default: %).
 @li ignoreSslError: ignore errors when adding SSL support for the new instance,
 by default: true.
+@li mysqldPath: path to the mysqld binary, or the MySQL installation root. Used
+to locate the server binary for sandbox deployment.
 @li mysqldOptions: list of MySQL configuration options to write to the my.cnf
 file, as option=value strings.
 
@@ -2305,8 +2307,8 @@ void Dba::deploy_sandbox_instance(
   shcore::Array_t errors;
   int rc = _provisioning_interface.create_sandbox(
       port, opts.xport.value_or(0), options->get_sandbox_dir(), *password,
-      shcore::Value(opts.mysqld_options), true, opts.ignore_ssl_error, 0, "",
-      &errors);
+      shcore::Value(opts.mysqld_options), true, opts.ignore_ssl_error, 0,
+      opts.mysqld_path, &errors);
 
   if (rc != 0) throw_instance_op_error(errors);
 
diff --git a/modules/adminapi/mod_dba_cluster.cc b/modules/adminapi/mod_dba_cluster.cc
index 07c2f2c3d3..8a76a3eb71 100644
--- a/modules/adminapi/mod_dba_cluster.cc
+++ b/modules/adminapi/mod_dba_cluster.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2016, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -127,7 +127,6 @@ void Cluster::init() {
       ->cli();
   expose("removeRouterMetadata", &Cluster::remove_router_metadata, "router")
       ->cli();
-  expose("routingOptions", &Cluster::routing_options, "?router")->cli();
   expose("routerOptions", &Cluster::router_options, "?options")->cli();
   expose("setRoutingOption", &Cluster::set_routing_option, "option", "value");
   expose("setRoutingOption", &Cluster::set_routing_option, "router", "option",
@@ -728,12 +727,6 @@ members or other potential issues.
 
 The options dictionary may contain the following attributes:
 
-@li addInstances: List with the connection data of the new active instances to
-add to the metadata, or "auto" to automatically add missing instances to the
-metadata. Deprecated.
-@li removeInstances: List with the connection data of the obsolete instances to
-remove from the metadata, or "auto" to automatically remove obsolete instances
-from the metadata. Deprecated.
 @li upgradeCommProtocol: Boolean. Set to true to upgrade the Group Replication
 communication protocol to the highest version possible.
 @li updateViewChangeUuid: Boolean. Indicates if the command should generate and
@@ -749,19 +742,10 @@ Defaults to false.
 @li repairMetadata: Boolean. Set to true to repair the Metadata if detected to
 be inconsistent.
 
-The value for 'addInstances' and 'removeInstances' is used to specify which
-instances to add or remove from the metadata, respectively. Both options accept
-list connection data. In addition, the "auto" value can be used for both
-options in order to automatically add or remove the instances in the metadata,
-without having to explicitly specify them.
-
 'repairMetadata' is used to eliminate any inconsistencies detected in the
 Metadata. These inconsistencies may arise from a few scenarios, such as the
 failure of one or more commands. Clusters detected in the ClusterSet Metadata
 that do not qualify as valid members will be removed.
-
-@attention The addInstances and removeInstances options will be removed in a
-future release. Use addUnmanaged and removeObsolete instead.
 )*");
 
 /**
@@ -1269,19 +1253,6 @@ Undefined Cluster::setRoutingOption(String router, String option,
 None Cluster::set_routing_option(str router, str option, str value) {}
 #endif
 
-REGISTER_HELP_FUNCTION(routingOptions, Cluster);
-REGISTER_HELP_FUNCTION_TEXT(CLUSTER_ROUTINGOPTIONS, ROUTINGOPTIONS_HELP_TEXT);
-/**
- * $(CLUSTER_ROUTINGOPTIONS_BRIEF)
- *
- * $(CLUSTER_ROUTINGOPTIONS)
- */
-#if DOXYGEN_JS
-Dictionary Cluster::routingOptions(String router) {}
-#elif DOXYGEN_PY
-dict Cluster::routing_options(str router) {}
-#endif
-
 REGISTER_HELP_FUNCTION(removeRouterMetadata, Cluster);
 REGISTER_HELP_FUNCTION_TEXT(CLUSTER_REMOVEROUTERMETADATA, R"*(
 Removes metadata for a router instance.
diff --git a/modules/adminapi/mod_dba_cluster.h b/modules/adminapi/mod_dba_cluster.h
index eb1eecdadc..b0a1609c6e 100644
--- a/modules/adminapi/mod_dba_cluster.h
+++ b/modules/adminapi/mod_dba_cluster.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2016, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -68,7 +68,6 @@ class Cluster : public std::enable_shared_from_this<Cluster>,
   Undefined setRoutingOption(String option, String value);
   Undefined setRoutingOption(String router, String option, String value);
   Dictionary routerOptions(Dictionary options);
-  Dictionary routingOptions(String router);
   Dictionary rejoinInstance(InstanceDef instance, Dictionary options);
   Undefined removeInstance(InstanceDef instance, Dictionary options);
   Undefined rescan(Dictionary options);
@@ -109,7 +108,6 @@ class Cluster : public std::enable_shared_from_this<Cluster>,
   None set_routing_option(str option, str value);
   None set_routing_option(str router, str option, str value);
   dict router_options(dict options);
-  dict routing_options(str router);
   dict rejoin_instance(InstanceDef instance, dict options);
   None remove_instance(InstanceDef instance, dict options);
   None rescan(dict options);
diff --git a/modules/adminapi/mod_dba_cluster_set.cc b/modules/adminapi/mod_dba_cluster_set.cc
index 5e91cb541e..f3dc4f8bb0 100644
--- a/modules/adminapi/mod_dba_cluster_set.cc
+++ b/modules/adminapi/mod_dba_cluster_set.cc
@@ -99,7 +99,6 @@ void ClusterSet::init() {
   expose("setOption", &ClusterSet::set_option, "option", "value")->cli();
 
   expose("listRouters", &ClusterSet::list_routers, "?router")->cli();
-  expose("routingOptions", &ClusterSet::routing_options, "?router")->cli();
   expose("routerOptions", &ClusterSet::router_options, "?options")->cli();
   // TODO(konrad): cli does not support yet such overloads
   expose("setRoutingOption", &ClusterSet::set_routing_option, "option",
@@ -1018,21 +1017,6 @@ Undefined ClusterSet::setRoutingOption(String router, String option,
 None ClusterSet::set_routing_option(str router, str option, str value) {}
 #endif
 
-REGISTER_HELP_FUNCTION(routingOptions, ClusterSet);
-REGISTER_HELP_FUNCTION_TEXT(CLUSTERSET_ROUTINGOPTIONS,
-                            ROUTINGOPTIONS_HELP_TEXT);
-
-/**
- * $(CLUSTERSET_ROUTINGOPTIONS_BRIEF)
- *
- * $(CLUSTERSET_ROUTINGOPTIONS)
- */
-#if DOXYGEN_JS
-Dictionary ClusterSet::routingOptions(String router) {}
-#elif DOXYGEN_PY
-dict ClusterSet::routing_options(str router) {}
-#endif
-
 REGISTER_HELP_FUNCTION(routerOptions, ClusterSet);
 REGISTER_HELP_FUNCTION_TEXT(CLUSTERSET_ROUTEROPTIONS, R"*(
 Lists the configuration options of the ClusterSet's Routers.
diff --git a/modules/adminapi/mod_dba_cluster_set.h b/modules/adminapi/mod_dba_cluster_set.h
index 7fa32a509e..f7a6ee431d 100644
--- a/modules/adminapi/mod_dba_cluster_set.h
+++ b/modules/adminapi/mod_dba_cluster_set.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2020, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -67,7 +67,6 @@ class ClusterSet : public std::enable_shared_from_this<ClusterSet>,
   Undefined setupAdminAccount(String user, Dictionary options);
   Undefined setupRouterAccount(String user, Dictionary options);
   Dictionary routerOptions(String router);
-  Dictionary routingOptions(String router);
   Dictionary listRouters(String router);
   Undefined setOption(String option, String value);
   Dictionary options();
@@ -96,7 +95,6 @@ class ClusterSet : public std::enable_shared_from_this<ClusterSet>,
   None setup_admin_account(str user, dict options);
   None setup_router_account(str user, dict options);
   dict router_options(dict options);
-  dict routing_options(str router);
   dict list_routers(str router);
   None set_option(str option, str value);
   dict options();
diff --git a/modules/adminapi/mod_dba_replica_set.cc b/modules/adminapi/mod_dba_replica_set.cc
index 5766afbb21..5c382f2ea5 100644
--- a/modules/adminapi/mod_dba_replica_set.cc
+++ b/modules/adminapi/mod_dba_replica_set.cc
@@ -104,7 +104,6 @@ void ReplicaSet::init() {
   expose("setupRouterAccount", &ReplicaSet::setup_router_account, "user",
          "?options")
       ->cli();
-  expose("routingOptions", &ReplicaSet::routing_options, "?router")->cli();
   expose("routerOptions", &ReplicaSet::router_options, "?options")->cli();
   expose("setRoutingOption", &ReplicaSet::set_routing_option, "option",
          "value");
@@ -918,21 +917,6 @@ Undefined ReplicaSet::setupRouterAccount(String user, Dictionary options) {}
 None ReplicaSet::setup_router_account(str user, dict options) {}
 #endif
 
-REGISTER_HELP_FUNCTION(routingOptions, ReplicaSet);
-REGISTER_HELP_FUNCTION_TEXT(REPLICASET_ROUTINGOPTIONS,
-                            ROUTINGOPTIONS_HELP_TEXT);
-
-/**
- * $(REPLICASET_ROUTINGOPTIONS_BRIEF)
- *
- * $(REPLICASET_ROUTINGOPTIONS)
- */
-#if DOXYGEN_JS
-Dictionary ReplicaSet::routingOptions(String router) {}
-#elif DOXYGEN_PY
-dict ReplicaSet::routing_options(str router) {}
-#endif
-
 REGISTER_HELP_FUNCTION(routerOptions, ReplicaSet);
 REGISTER_HELP_FUNCTION_TEXT(REPLICASET_ROUTEROPTIONS, R"*(
 Lists the configuration options of the ReplicaSet's Routers.
diff --git a/modules/adminapi/mod_dba_replica_set.h b/modules/adminapi/mod_dba_replica_set.h
index 25ccd8b3ee..2ef34e3cb0 100644
--- a/modules/adminapi/mod_dba_replica_set.h
+++ b/modules/adminapi/mod_dba_replica_set.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2016, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -63,7 +63,6 @@ class ReplicaSet : public std::enable_shared_from_this<ReplicaSet>,
   Undefined setRoutingOption(String option, String value);
   Undefined setRoutingOption(String router, String option, String value);
   Dictionary routerOptions(Dictionary options);
-  Dictionary routingOptions(String router);
   Undefined rejoinInstance(String instance, Dictionary options);
   Undefined removeInstance(String instance, Dictionary options);
   Dictionary status(Dictionary options);
@@ -94,7 +93,6 @@ class ReplicaSet : public std::enable_shared_from_this<ReplicaSet>,
   None set_routing_option(str option, str value);
   None set_routing_option(str router, str option, str value);
   dict router_options(dict options);
-  dict routing_options(str router);
   None rejoin_instance(str instance, dict options);
   None remove_instance(str instance, dict options);
   dict status(dict options);
diff --git a/modules/adminapi/mod_dba_routing_guideline.cc b/modules/adminapi/mod_dba_routing_guideline.cc
index 1c67531f82..0497563298 100644
--- a/modules/adminapi/mod_dba_routing_guideline.cc
+++ b/modules/adminapi/mod_dba_routing_guideline.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2024, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -415,7 +415,7 @@ splitting connections.
 @li $.router.localCluster: Name of the cluster configured as local to the
 Router.
 @li $.router.hostname: Hostname where the Router is running.
-@li $.router.address: IP address on which the Router is listening.
+@li $.router.bindAddress: IP address on which the Router is listening.
 @li $.router.tags: A key-value object containing user-defined tags stored in
 the topology's metadata for that Router instance.
 @li $.router.routeName: Name of the Routing plugin used by the Router.
@@ -513,7 +513,7 @@ splitting connections.
 @li $.router.localCluster: Name of the cluster configured as local to the
 Router.
 @li $.router.hostname: Hostname where Router is running.
-@li $.router.address: IP address on which the Router is listening.
+@li $.router.bindAddress: IP address on which the Router is listening.
 @li $.router.tags: A key-value object containing user-defined tags stored in
 the topology's metadata for that Router instance.
 @li $.router.routeName: Name of the Routing plugin used by the Router.
@@ -702,7 +702,7 @@ servers of the target topology that match the destinations and routes.
 It also identifies all destinations not referenced by any route.
 
 Destinations that rely on expressions involving the
-target router port or address (e.g., '$.router.port' or '$.router.address')
+target router port or address (e.g., '$.router.port' or '$.router.bindAddress')
 may produce results that differ from actual behavior in MySQL Router. Since
 this function evaluates the guideline outside the context of the router,
 these expressions are not matched against live router runtime data and may
@@ -744,11 +744,13 @@ void RoutingGuideline::show(
 
 REGISTER_HELP_FUNCTION(rename, RoutingGuideline);
 REGISTER_HELP_FUNCTION_TEXT(ROUTINGGUIDELINE_RENAME, R"*(
-Rename the target Routing Guideline.
+Renames the target Routing Guideline.
 
-This functions allows changing the name of an existing Routing Guideline.
+@param name The new name to assign to the Routing Guideline.
 
-@returns Nothing
+@returns Nothing.
+
+This command renames the target Routing Guideline.
 )*");
 
 /**
diff --git a/modules/devapi/mod_mysqlx_session.cc b/modules/devapi/mod_mysqlx_session.cc
index fd17471ba1..eec3391e9e 100644
--- a/modules/devapi/mod_mysqlx_session.cc
+++ b/modules/devapi/mod_mysqlx_session.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2014, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -148,6 +148,10 @@ void Session::init() {
 
   expose("runSql", &Session::run_sql, "query", "?args");
 
+  expose("setClientData", &Session::set_client_data, "key", "data");
+  expose("getClientData", &Session::get_client_data, "key");
+  expose("getSqlMode", &Session::get_sql_mode);
+
   expose("_getSocketFd", &Session::_get_socket_fd);
   expose("_fetchNotice", &Session::_fetch_notice);
   expose("_enableNotices", &Session::_enable_notices, "noticeTypes");
@@ -444,7 +448,7 @@ REGISTER_HELP_FUNCTION(getUri, Session);
 REGISTER_HELP_FUNCTION_TEXT(SESSION_GETURI, R"*(
 Retrieves the URI for the current session.
 
-@return A string representing the connection data.
+@returns A string representing the connection data.
 )*");
 /**
  * $(SESSION_GETURI_BRIEF)
@@ -464,7 +468,7 @@ REGISTER_HELP_FUNCTION(getCurrentSchema, Session);
 REGISTER_HELP_FUNCTION_TEXT(SESSION_GETCURRENTSCHEMA, R"*(
 Retrieves the active schema on the session.
 
-@return A Schema object if a schema is active on the session.
+@returns A Schema object if a schema is active on the session.
 )*");
 /**
  * $(SESSION_GETCURRENTSCHEMA_BRIEF)
@@ -905,6 +909,49 @@ std::shared_ptr<SqlResult> Session::_rollback() {
   return std::make_shared<SqlResult>(result);
 }
 
+REGISTER_HELP_FUNCTION(setClientData, Session);
+REGISTER_HELP_FUNCTION_TEXT(SESSION_SETCLIENTDATA, R"*(
+Associates a value with the session for the given key.
+
+@param key (string) A string to identify the stored value.
+@param value JSON-like value to be stored.
+
+Saves a value in the session data structure associated to the given key.
+The value can be retrieved later using <<<getClientData>>>().
+)*");
+/**
+ * $(SESSION_SETCLIENTDATA_BRIEF)
+ *
+ * $(SESSION_SETCLIENTDATA)
+ */
+#if DOXYGEN_JS
+Undefined Session::setClientData(String key, Any value) {}
+#elif DOXYGEN_PY
+None Session::set_client_data(str key, Any value) {}
+#endif
+
+REGISTER_HELP_FUNCTION(getClientData, Session);
+REGISTER_HELP_FUNCTION_TEXT(SESSION_GETCLIENTDATA, R"*(
+Returns value associated with the session for the given key.
+
+@param key (string) A string to identify the stored value.
+
+@returns JSON-like value stored for the key.
+
+Returns a value previously stored in the session data structure associated
+with <<<setClientData>>>().
+)*");
+/**
+ * $(SESSION_GETCLIENTDATA_BRIEF)
+ *
+ * $(SESSION_GETCLIENTDATA)
+ */
+#if DOXYGEN_JS
+Any Session::getClientData(String key) {}
+#elif DOXYGEN_PY
+Any Session::get_client_data(str key) {}
+#endif
+
 std::string Session::query_one_string(const std::string &query, int field) {
   auto result = execute_sql(query);
   if (auto row = result->fetch_one()) {
@@ -1042,11 +1089,15 @@ std::shared_ptr<shcore::Object_bridge> Session::create(
                                      mysqlshdk::db::kSslModeRequired);
   }
 
-  // before creating a normal session we need to establish ssh if needed:
   co.set_default_data();
-  auto &ssh = co.get_ssh_options_handle(mysqlshdk::db::k_default_mysql_x_port);
-  if (ssh.has_data()) {
-    mysqlshdk::ssh::current_ssh_manager()->create_tunnel(&ssh);
+  co.get_ssh_options().set_fallback_remote_port(
+      mysqlshdk::db::k_default_mysql_x_port);
+
+  mysqlshdk::ssh::Ssh_tunnel tunnel;
+
+  if (auto &ssh = co.get_ssh_options(); ssh.has_data()) {
+    // before creating a normal session we need to establish ssh if needed:
+    tunnel = mysqlshdk::ssh::current_ssh_manager()->create_tunnel(&ssh);
   }
 
   const auto session = std::make_shared<Session>();
@@ -1066,7 +1117,7 @@ target MySQL Server.
 
 @param sql A string containing the SQL statement to be executed.
 
-@return A SqlExecute object.
+@returns A SqlExecute object.
 
 This method creates an SqlExecute object which is a SQL execution handler.
 
@@ -1148,7 +1199,7 @@ REGISTER_HELP_FUNCTION(getSshUri, Session);
 REGISTER_HELP_FUNCTION_TEXT(SESSION_GETSSHURI, R"*(
 Retrieves the SSH URI for the current session.
 
-@return A string representing the SSH connection data.
+@returns A string representing the SSH connection data.
 )*");
 /**
  * $(SESSION_GETSSHURI_BRIEF)
@@ -1161,13 +1212,32 @@ String Session::getSshUri() {}
 str Session::get_ssh_uri() {}
 #endif
 
+REGISTER_HELP_FUNCTION(getSqlMode, Session);
+REGISTER_HELP_FUNCTION_TEXT(SESSION_GETSQLMODE, R"*(
+Retrieves the SQL_MODE for the current session.
+
+@returns Value of the SQL_MODE session variable.
+
+Queries the value of the SQL_MODE session variable.
+)*");
+/**
+ * $(SESSION_GETSQLMODE_BRIEF)
+ *
+ * $(SESSION_GETSQLMODE)
+ */
+#if DOXYGEN_JS
+String Session::getSqlMode() {}
+#elif DOXYGEN_PY
+str Session::get_sql_mode() {}
+#endif
+
 REGISTER_HELP_PROPERTY(connectionId, Session);
 REGISTER_HELP(SESSION_CONNECTIONID_BRIEF, "${SESSION_GETCONNECTIONID_BRIEF}");
 REGISTER_HELP_FUNCTION(getConnectionId, Session);
 REGISTER_HELP_FUNCTION_TEXT(SESSION_GETCONNECTIONID, R"*(
 Retrieves the connection id for the current session.
 
-@return An integer value representing the connection id.
+@returns An integer value representing the connection id.
 )*");
 /**
  * $(SESSION_GETCONNECTIONID_BRIEF)
@@ -1224,7 +1294,7 @@ Escapes the passed identifier.
 
 @param id The identifier to be quoted.
 
-@return A String containing the escaped identifier.
+@returns A String containing the escaped identifier.
 )*");
 /**
  * $(SESSION_QUOTENAME_BRIEF)
@@ -1247,7 +1317,7 @@ Sets the current schema for this session, and returns the schema object for it.
 
 @param name the name of the new schema to switch to.
 
-@return the Schema object for the new schema.
+@returns the Schema object for the new schema.
 
 At the database level, this is equivalent at issuing the following SQL query:
 
diff --git a/modules/devapi/mod_mysqlx_session.h b/modules/devapi/mod_mysqlx_session.h
index e66cc3c2e4..1d2b978caa 100644
--- a/modules/devapi/mod_mysqlx_session.h
+++ b/modules/devapi/mod_mysqlx_session.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2015, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -90,6 +90,7 @@ class SHCORE_PUBLIC Session : public ShellBaseSession,
   List getSchemas();
   String getUri();
   String getSshUri();
+  String getSqlMode();
   Integer getConnectionId();
   Undefined close();
   Undefined setFetchWarnings(Boolean enable);
@@ -105,6 +106,8 @@ class SHCORE_PUBLIC Session : public ShellBaseSession,
   Undefined releaseSavepoint(String name);
   Undefined rollbackTo(String name);
   SqlResult runSql(String query, Array args);
+  Undefined setClientData(String key, Any value);
+  Any getClientData(String key);
 
  private:
 #elif DOXYGEN_PY
@@ -122,6 +125,7 @@ class SHCORE_PUBLIC Session : public ShellBaseSession,
   list get_schemas();
   str get_uri();
   str get_ssh_uri();
+  str get_sql_mode();
   int get_connection_id();
   None close();
   None set_fetch_warnings(bool enable);
@@ -137,6 +141,8 @@ class SHCORE_PUBLIC Session : public ShellBaseSession,
   None release_savepoint(str name);
   None rollback_to(str name);
   SqlResult run_sql(str query, list args);
+  None set_client_data(str key, Any value);
+  Any get_client_data(str key);
 
  private:
 #endif
diff --git a/modules/mod_mysql.cc b/modules/mod_mysql.cc
index d24fe7feef..949fdb74d8 100644
--- a/modules/mod_mysql.cc
+++ b/modules/mod_mysql.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2016, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -39,6 +39,7 @@
 #include "mysqlshdk/libs/db/utils/utils.h"
 #include "mysqlshdk/libs/parser/mysql_parser_utils.h"
 #include "mysqlshdk/libs/utils/utils_general.h"
+#include "mysqlshdk/libs/utils/utils_lexing.h"
 #include "mysqlshdk/libs/utils/utils_mysql_parsing.h"
 
 #include "errmsg.h"  // NOLINT
@@ -211,6 +212,7 @@ REGISTER_MODULE(Mysql, mysql) {
 
   expose("splitScript", &Mysql::split_script, "script");
   expose("parseStatementAst", &Mysql::parse_statement_ast, "statement");
+  expose("tokenizeStatement", &Mysql::tokenize_statement, "statement");
 
   expose("quoteIdentifier", &Mysql::quote_identifier, "string");
   expose("unquoteIdentifier", &Mysql::unquote_identifier, "string");
@@ -423,6 +425,40 @@ shcore::Value Mysql::parse_statement_ast(const std::string &sql) const {
   return listener.ast();
 }
 
+REGISTER_HELP_FUNCTION(tokenizeStatement, mysql);
+REGISTER_HELP_FUNCTION_TEXT(MYSQL_TOKENIZESTATEMENT, R"*(
+Lexes a MySQL statement into a list of tokens.
+
+@param statement SQL statement to be tokenized
+
+@returns A list of objects with type and token fields
+)*");
+
+/**
+ * \ingroup mysql
+ * $(MYSQL_TOKENIZESTATEMENT_BRIEF)
+ *
+ * $(MYSQL_TOKENIZESTATEMENT)
+ */
+#if DOXYGEN_JS
+Array tokenizeStatement(String statement) {}
+#elif DOXYGEN_PY
+list tokenize_statement(str statement) {}
+#endif
+shcore::Value Mysql::tokenize_statement(const std::string &sql) const {
+  shcore::Array_t tokens = shcore::make_array();
+  mysqlshdk::parser::Parser_config config;
+
+  mysqlshdk::parser::tokenize_statement(
+      sql, config, [&tokens](std::string_view type, std::string_view token) {
+        tokens->push_back(shcore::Value(shcore::make_dict(
+            "type", shcore::Value(type), "token", shcore::Value(token))));
+        return true;
+      });
+
+  return shcore::Value(tokens);
+}
+
 REGISTER_HELP_FUNCTION(quoteIdentifier, mysql);
 REGISTER_HELP_FUNCTION_TEXT(MYSQL_QUOTEIDENTIFIER, R"*(
 Quote a string as a MySQL identifier, escaping characters when needed.
diff --git a/modules/mod_mysql.h b/modules/mod_mysql.h
index 65120fd297..ca9ae167b9 100644
--- a/modules/mod_mysql.h
+++ b/modules/mod_mysql.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2016, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -80,6 +80,7 @@ ClassicSession getClassicSession(ConnectionData connectionData,
 ClassicSession getSession(ConnectionData connectionData, String password);
 Array splitScript(String script);
 Dictionary parseStatementAst(String statements);
+Array tokenizeStatement(String statement);
 String quoteIdentifier(String s);
 String unquoteIdentifier(String s);
 #elif DOXYGEN_PY
@@ -87,6 +88,7 @@ ClassicSession get_classic_session(ConnectionData connectionData, str password);
 ClassicSession get_session(ConnectionData connectionData, str password);
 list split_script(str script);
 dict parse_statement_ast(str statements);
+list tokenize_statement(str statement);
 str quote_identifier(str s);
 str unquote_identifier(str s);
 #endif
@@ -104,6 +106,7 @@ virtual shcore::Value get_member(const std::string &prop) const;
 
 shcore::Value split_script(const std::string &script) const;
 shcore::Value parse_statement_ast(const std::string &statement) const;
+shcore::Value tokenize_statement(const std::string &statement) const;
 
 std::string quote_identifier(const std::string &s) const;
 std::string unquote_identifier(const std::string &s) const;
diff --git a/modules/mod_mysql_session.cc b/modules/mod_mysql_session.cc
index a9726da230..336fb92003 100644
--- a/modules/mod_mysql_session.cc
+++ b/modules/mod_mysql_session.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2014, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -94,6 +94,11 @@ void ClassicSession::init() {
   expose("rollback", &ClassicSession::_rollback);
   expose("setQueryAttributes", &ClassicSession::set_query_attributes,
          "attributes");
+  expose("setClientData", &ClassicSession::set_client_data, "key", "data");
+  expose("getClientData", &ClassicSession::get_client_data, "key");
+  expose("getSqlMode", &ClassicSession::get_sql_mode);
+  expose("trackSystemVariable", &ClassicSession::track_system_variable,
+         "variable");
 
   expose("_getSocketFd", &ClassicSession::_get_socket_fd);
 }
@@ -288,9 +293,8 @@ std::shared_ptr<mysqlshdk::db::IResult> ClassicSession::do_execute_sql(
           });
 
       try {
-        result =
-            _session->query(sub_query_placeholders(std::string(query), args),
-                            false, query_attributes);
+        result = _session->query(sub_query_placeholders(query, args), false,
+                                 query_attributes);
       } catch (const mysqlshdk::db::Error &error) {
         throw shcore::Exception::mysql_error_with_code_and_state(
             error.what(), error.code(), error.sqlstate());
@@ -308,7 +312,7 @@ REGISTER_HELP_FUNCTION(getUri, ClassicSession);
 REGISTER_HELP_FUNCTION_TEXT(CLASSICSESSION_GETURI, R"*(
 Retrieves the URI for the current session.
 
-@return A string representing the connection data.
+@returns A string representing the connection data.
 )*");
 /**
  * $(CLASSICSESSION_GETURI_BRIEF)
@@ -327,7 +331,7 @@ REGISTER_HELP(CLASSICSESSION_SSHURI_BRIEF, "${CLASSICSESSION_GETSSHURI_BRIEF}");
 REGISTER_HELP_FUNCTION_TEXT(CLASSICSESSION_GETSSHURI, R"*(
 Retrieves the SSH URI for the current session.
 
-@return A string representing the SSH connection data.
+@returns A string representing the SSH connection data.
 )*");
 /**
  * $(CLASSICSESSION_GETSSHURI_BRIEF)
@@ -340,6 +344,26 @@ String ClassicSession::getSshUri() {}
 str ClassicSession::get_ssh_uri() {}
 #endif
 
+REGISTER_HELP_FUNCTION(getSqlMode, ClassicSession);
+REGISTER_HELP_FUNCTION_TEXT(CLASSICSESSION_GETSQLMODE, R"*(
+Retrieves the SQL_MODE for the current session.
+
+@returns Value of the SQL_MODE session variable.
+
+Queries the value of the SQL_MODE session variable. If session tracking of
+SQL_MODE is enabled, it will fetch its cached value.
+)*");
+/**
+ * $(CLASSICSESSION_GETSQLMODE_BRIEF)
+ *
+ * $(CLASSICSESSION_GETSQLMODE)
+ */
+#if DOXYGEN_JS
+String ClassicSession::getSqlMode() {}
+#elif DOXYGEN_PY
+str ClassicSession::get_sql_mode() {}
+#endif
+
 REGISTER_HELP_PROPERTY(connectionId, ClassicSession);
 REGISTER_HELP_FUNCTION(getConnectionId, ClassicSession);
 REGISTER_HELP(CLASSICSESSION_CONNECTIONID_BRIEF,
@@ -347,7 +371,7 @@ REGISTER_HELP(CLASSICSESSION_CONNECTIONID_BRIEF,
 REGISTER_HELP_FUNCTION_TEXT(CLASSICSESSION_GETCONNECTIONID, R"*(
 Retrieves the connection id for the current session.
 
-@return An integer value representing the connection id.
+@returns An integer value representing the connection id.
 )*");
 /**
  * $(CLASSICSESSION_GETCONNECTIONID_BRIEF)
@@ -402,6 +426,49 @@ void ClassicSession::set_current_schema(const std::string &name) {
   get_core_session()->execute(query.str_view());
 }
 
+REGISTER_HELP_FUNCTION(setClientData, ClassicSession);
+REGISTER_HELP_FUNCTION_TEXT(CLASSICSESSION_SETCLIENTDATA, R"*(
+Associates a value with the session for the given key.
+
+@param key (string) A string to identify the stored value.
+@param value JSON-like value to be stored.
+
+Saves a value in the session data structure associated to the given key.
+The value can be retrieved later using <<<getClientData>>>().
+)*");
+/**
+ * $(CLASSICSESSION_SETCLIENTDATA_BRIEF)
+ *
+ * $(CLASSICSESSION_SETCLIENTDATA)
+ */
+#if DOXYGEN_JS
+Undefined ClassicSession::setClientData(String key, Any value) {}
+#elif DOXYGEN_PY
+None ClassicSession::set_client_data(str key, Any value) {}
+#endif
+
+REGISTER_HELP_FUNCTION(getClientData, ClassicSession);
+REGISTER_HELP_FUNCTION_TEXT(CLASSICSESSION_GETCLIENTDATA, R"*(
+Returns value associated with the session for the given key.
+
+@param key (string) A string to identify the stored value.
+
+@returns JSON-like value stored for the key.
+
+Returns a value previously stored in the session data structure associated
+with <<<setClientData>>>().
+)*");
+/**
+ * $(CLASSICSESSION_GETCLIENTDATA_BRIEF)
+ *
+ * $(CLASSICSESSION_GETCLIENTDATA)
+ */
+#if DOXYGEN_JS
+Any ClassicSession::getClientData(String key) {}
+#elif DOXYGEN_PY
+Any ClassicSession::get_client_data(str key) {}
+#endif
+
 std::shared_ptr<shcore::Object_bridge> ClassicSession::create(
     const mysqlshdk::db::Connection_options &co_) {
   auto co = co_;
@@ -413,11 +480,15 @@ std::shared_ptr<shcore::Object_bridge> ClassicSession::create(
     co.get_ssl_options().set_mode(mysqlshdk::db::Ssl_mode::Required);
   }
 
-  // before creating a normal session we need to establish ssh if needed:
   co.set_default_data();
-  auto &ssh = co.get_ssh_options_handle(mysqlshdk::db::k_default_mysql_port);
-  if (ssh.has_data()) {
-    mysqlshdk::ssh::current_ssh_manager()->create_tunnel(&ssh);
+  co.get_ssh_options().set_fallback_remote_port(
+      mysqlshdk::db::k_default_mysql_port);
+
+  mysqlshdk::ssh::Ssh_tunnel tunnel;
+
+  if (auto &ssh = co.get_ssh_options(); ssh.has_data()) {
+    // before creating a normal session we need to establish ssh if needed:
+    tunnel = mysqlshdk::ssh::current_ssh_manager()->create_tunnel(&ssh);
   }
 
   const auto session = std::make_shared<ClassicSession>();
@@ -693,3 +764,32 @@ socket_t ClassicSession::_get_socket_fd() const {
     throw std::invalid_argument("Session is not open");
   return _session->get_socket_fd();
 }
+
+REGISTER_HELP_FUNCTION(trackSystemVariable, ClassicSession);
+REGISTER_HELP_FUNCTION_TEXT(CLASSICSESSION_TRACKSYSTEMVARIABLE, R"*(
+Enables session tracking of the given system variable.
+
+@param variable Name of the system variable to track.
+@returns The new value of the session_track_system_variables session variable.
+
+Appends the given system variable name to the session_track_system_variables,
+session variable enabling session tracking of changes to it.
+Currently supported variables:
+@li SQL_MODE
+)*");
+/**
+ * $(CLASSICSESSION_TRACKSYSTEMVARIABLE_BRIEF)
+ *
+ * $(CLASSICSESSION_TRACKSYSTEMVARIABLE)
+ */
+#if DOXYGEN_JS
+String ClassicSession::trackSystemVariable(String variable) {}
+#elif DOXYGEN_PY
+str ClassicSession::track_system_variable(str variable) {}
+#endif
+std::string ClassicSession::track_system_variable(const std::string &variable) {
+  if (!_session || !_session->is_open())
+    throw std::invalid_argument("Session is not open");
+
+  return ShellBaseSession::track_system_variable(variable);
+}
diff --git a/modules/mod_mysql_session.h b/modules/mod_mysql_session.h
index 3a08c77b39..0be0863172 100644
--- a/modules/mod_mysql_session.h
+++ b/modules/mod_mysql_session.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2015, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -30,6 +30,7 @@
 #define _MOD_SESSION_H_
 
 #include <memory>
+#include <string>
 #include <string_view>
 
 #include "modules/mod_common.h"
@@ -79,6 +80,7 @@ class SHCORE_PUBLIC ClassicSession
 
   String getUri();
   String getSshUri();
+  String getSqlMode();
   Integer getConnectionId();
   ClassicResult runSql(String query, Array args = []);
   Undefined close();
@@ -87,6 +89,9 @@ class SHCORE_PUBLIC ClassicSession
   ClassicResult rollback();
   Bool isOpen();
   Undefined setQueryAttributes(Dictionary attributes);
+  Undefined setClientData(String key, Any value);
+  Any getClientData(String key);
+  String trackSystemVariable(String variable);
 
  private:
 #elif DOXYGEN_PY
@@ -96,6 +101,7 @@ class SHCORE_PUBLIC ClassicSession
 
   str get_uri();
   str get_ssh_uri();
+  str get_sql_mode();
   str get_connection_id();
   ClassicResult run_sql(str query, list args = []);
   None close();
@@ -104,6 +110,9 @@ class SHCORE_PUBLIC ClassicSession
   ClassicResult rollback();
   bool is_open();
   None set_query_attributes(dict attributes);
+  None set_client_data(str key, Any value);
+  Any get_client_data(str key);
+  str track_system_variable(str variable);
 
  private:
 #endif
@@ -158,6 +167,8 @@ class SHCORE_PUBLIC ClassicSession
 
   bool is_open() const override;
 
+  std::string track_system_variable(const std::string &variable) override;
+
  private:
   void init();
   std::shared_ptr<mysqlshdk::db::IResult> do_execute_sql(
diff --git a/modules/mod_shell.cc b/modules/mod_shell.cc
index 825227dd53..80794393ba 100644
--- a/modules/mod_shell.cc
+++ b/modules/mod_shell.cc
@@ -25,6 +25,8 @@
 #include "modules/mod_shell.h"
 
 #include <memory>
+#include <string>
+#include <utility>
 #include <vector>
 
 #include "modules/devapi/mod_mysqlx_schema.h"
@@ -216,6 +218,11 @@ void Shell::init() {
       ->cli(false);
   expose("listSqlHandlers", &Shell::list_sql_handlers)->cli(true);
   expose("createResult", &Shell::create_result, "?data")->cli(false);
+  expose("storeSecret", &Shell::store_secret, "key", "?value")->cli();
+  expose("readSecret", &Shell::read_secret, "key")->cli();
+  expose("deleteSecret", &Shell::delete_secret, "key")->cli();
+  expose("deleteAllSecrets", &Shell::delete_all_secrets)->cli();
+  expose("listSecrets", &Shell::list_secrets)->cli();
 }
 
 Shell::~Shell() {}
@@ -1254,27 +1261,31 @@ None Shell::status() {}
 void Shell::status() { _shell->cmd_status({}); }
 
 REGISTER_HELP_FUNCTION(listCredentialHelpers, shell);
-REGISTER_HELP(SHELL_LISTCREDENTIALHELPERS_BRIEF,
-              "Returns a list of strings, where each string is a name of a "
-              "helper available on the current platform.");
-REGISTER_HELP(
-    SHELL_LISTCREDENTIALHELPERS_RETURNS,
-    "@returns A list of string with names of available credential helpers.");
-REGISTER_HELP(SHELL_LISTCREDENTIALHELPERS_DETAIL,
-              "The special values \"default\" and \"@<disabled>\" "
-              "are not on the list.");
-REGISTER_HELP(SHELL_LISTCREDENTIALHELPERS_DETAIL1,
-              "Only values on this list (plus \"default\" and "
-              "\"@<disabled>\") can be used to set the "
-              "\"credentialStore.helper\" option.");
+REGISTER_HELP_FUNCTION_TEXT(SHELL_LISTCREDENTIALHELPERS, R"*(
+Returns a list of strings, where each string is a name of a helper available on
+the current platform.
+
+@returns A list of string with names of available credential helpers.
+
+The special values "default" and "<disabled>" are not on the list.
+
+Only values on this list (plus "default" and "<disabled>") can be used to set
+the "credentialStore.helper" %Shell option.
+
+<b>Limitations:</b>
+@li The <b>login-path</b> helper cannot store secrets longer than 78 characters.
+@li The <b>login-path</b> helper cannot store the following characters: '\\0',
+    '0x03', '0x04', '\\n', '\\r', '0x0F', '0x11', '0x12', '0x13', '0x15',
+    '0x16', '0x17', '0x19', '0x1A', '0x1C', '0x7F'.
+@li The <b>keychain</b> helper cannot store the following characters: '\\0',
+    '\\n'.
+@li The <b>secret-service</b> helper can only store UTF-8 encoded strings.
+)*");
 
 /**
  * $(SHELL_LISTCREDENTIALHELPERS_BRIEF)
  *
- * $(SHELL_LISTCREDENTIALHELPERS_RETURNS)
- *
- * $(SHELL_LISTCREDENTIALHELPERS_DETAIL)
- * $(SHELL_LISTCREDENTIALHELPERS_DETAIL1)
+ * $(SHELL_LISTCREDENTIALHELPERS)
  */
 #if DOXYGEN_JS
 List Shell::listCredentialHelpers() {}
@@ -2422,4 +2433,139 @@ std::shared_ptr<ShellResult> Shell::create_result(
   throw std::runtime_error("Invalid data for ShellResult");
 }
 
+namespace {
+
+void validate_key(const std::string &key) {
+  if (key.empty()) {
+    throw std::invalid_argument{"Key cannot be empty."};
+  }
+}
+
+}  // namespace
+
+REGISTER_HELP_FUNCTION(storeSecret, shell);
+REGISTER_HELP_FUNCTION_TEXT(SHELL_STORESECRET, R"*(
+Stores given secret using the configured helper.
+
+@param key A key that uniquely identifies the secret.
+@param value Optional value for the given key.
+
+If value is not provided, displays a prompt to enter the secret.
+
+If secret with the given key is already in the storage, its value is overwritten.
+
+The current helper is set by the <b>credentialStore.helper</b> %Shell option.
+
+The limitations of helpers on allowed characters do not apply to secrets stored
+by <<<storeSecret>>>().
+)*");
+
+/**
+ * $(SHELL_STORESECRET_BRIEF)
+ *
+ * $(SHELL_STORESECRET)
+ */
+#if DOXYGEN_JS
+Undefined Shell::storeSecret(String key, String value) {}
+#elif DOXYGEN_PY
+None Shell::store_secret(str key, str value) {}
+#endif
+void Shell::store_secret(const std::string &key,
+                         std::optional<std::string> value) {
+  validate_key(key);
+
+  std::string secret_to_store;
+
+  if (!value.has_value()) {
+    if (current_console()->prompt_password(
+            "Please provide the secret to store: ", &secret_to_store) !=
+        shcore::Prompt_result::Ok) {
+      throw shcore::cancelled("Cancelled");
+    }
+  } else {
+    secret_to_store = std::move(*value);
+  }
+
+  shcore::Credential_manager::get().store_secret(key, secret_to_store);
+}
+
+REGISTER_HELP_FUNCTION(readSecret, shell);
+REGISTER_HELP_FUNCTION_TEXT(SHELL_READSECRET, R"*(
+Reads secret for the given key using the configured helper.
+
+@param key A key of the secret to read.
+
+@returns Secret associated with the given key.
+)*");
+
+/**
+ * $(SHELL_READSECRET_BRIEF)
+ *
+ * $(SHELL_READSECRET)
+ */
+#if DOXYGEN_JS
+String Shell::readSecret(String key) {}
+#elif DOXYGEN_PY
+str Shell::read_secret(str key) {}
+#endif
+std::string Shell::read_secret(const std::string &key) {
+  validate_key(key);
+  return shcore::Credential_manager::get().read_secret(key);
+}
+
+REGISTER_HELP_FUNCTION(deleteSecret, shell);
+REGISTER_HELP_FUNCTION_TEXT(SHELL_DELETESECRET, R"*(
+Deletes secret for the given key using the configured helper.
+
+@param key A key of the secret to delete.
+)*");
+
+/**
+ * $(SHELL_DELETESECRET_BRIEF)
+ *
+ * $(SHELL_DELETESECRET)
+ */
+#if DOXYGEN_JS
+Undefined Shell::deleteSecret(String key) {}
+#elif DOXYGEN_PY
+None Shell::delete_secret(str key) {}
+#endif
+void Shell::delete_secret(const std::string &key) {
+  validate_key(key);
+  shcore::Credential_manager::get().delete_secret(key);
+}
+
+REGISTER_HELP_FUNCTION(deleteAllSecrets, shell);
+REGISTER_HELP(SHELL_DELETEALLSECRETS_BRIEF,
+              "Deletes all secrets managed by the configured helper.");
+
+/**
+ * $(SHELL_DELETEALLSECRETS_BRIEF)
+ */
+#if DOXYGEN_JS
+Undefined Shell::deleteAllSecrets() {}
+#elif DOXYGEN_PY
+None Shell::delete_all_secrets() {}
+#endif
+void Shell::delete_all_secrets() {
+  shcore::Credential_manager::get().delete_all_secrets();
+}
+
+REGISTER_HELP_FUNCTION(listSecrets, shell);
+REGISTER_HELP(
+    SHELL_LISTSECRETS_BRIEF,
+    "Retrieves a list of all secrets' keys stored by the configured helper.");
+
+/**
+ * $(SHELL_LISTSECRETS_BRIEF)
+ */
+#if DOXYGEN_JS
+List Shell::listSecrets() {}
+#elif DOXYGEN_PY
+list Shell::list_secrets() {}
+#endif
+shcore::Array_t Shell::list_secrets() {
+  return shcore::make_array(shcore::Credential_manager::get().list_secrets());
+}
+
 }  // namespace mysqlsh
diff --git a/modules/mod_shell.h b/modules/mod_shell.h
index e3f2807114..9f628bf5e9 100644
--- a/modules/mod_shell.h
+++ b/modules/mod_shell.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2016, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -96,6 +96,11 @@ class SHCORE_PUBLIC Shell : public shcore::Cpp_object_bridge
   List listSqlHandlers();
   ShellResult createResult(Dictionary data);
   ShellResult createResult(List data);
+  Undefined storeSecret(String key, String value);
+  String readSecret(String key);
+  Undefined deleteSecret(String key);
+  Undefined deleteAllSecrets();
+  List listSecrets();
 
  private:
 #elif DOXYGEN_PY
@@ -136,6 +141,11 @@ class SHCORE_PUBLIC Shell : public shcore::Cpp_object_bridge
   list list_sql_handlers();
   ShellResult create_result(dict data);
   ShellResult create_result(list data);
+  None store_secret(str key, str value);
+  str read_secret(str key);
+  None delete_secret(str key);
+  None delete_all_secrets();
+  list list_secrets();
 
  private:
 #endif
@@ -244,6 +254,12 @@ class SHCORE_PUBLIC Shell : public shcore::Cpp_object_bridge
       const shcore::Option_pack_ref<mysqlshdk::Auto_complete_sql_options>
           &options) const;
 
+  void store_secret(const std::string &key, std::optional<std::string> value);
+  std::string read_secret(const std::string &key);
+  void delete_secret(const std::string &key);
+  void delete_all_secrets();
+  shcore::Array_t list_secrets();
+
  protected:
   void init();
 
diff --git a/modules/mod_shell_options.cc b/modules/mod_shell_options.cc
index 8be502b282..d6fc145e89 100644
--- a/modules/mod_shell_options.cc
+++ b/modules/mod_shell_options.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2017, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -73,11 +73,6 @@ clusterset, or adding an instance to one
 @li <b>dba.gtidWaitTimeout</b>: timeout value in seconds to wait for GTIDs to be
 synchronized
 
-@li <b>dba.logSql</b>: 0..2, log SQL statements executed by AdminAPI operations:
-0 - logging disabled; 1 - log statements other than SELECT and SHOW; 2 - log
-all statements. Option takes precedence over <b>--log-sql</b> in Dba.* context,
-if enabled.
-
 @li <b>dba.restartWaitTimeout</b>: timeout in seconds to wait for MySQL server
 to come back after a restart during clone recovery
 
@@ -140,8 +135,6 @@ configuration file
 @li <b>oci.profile</b>: Specify which section in oci.configFile will be used as
 profile settings
 
-@li <b>outputFormat</b>: Deprecated, use <b>resultFormat</b> instead
-
 @li <b>pager</b>: string which specifies the external command which is going to
 be used to display the paged output
 
diff --git a/modules/mod_utils.cc b/modules/mod_utils.cc
index 132e81e712..5372f01129 100644
--- a/modules/mod_utils.cc
+++ b/modules/mod_utils.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2017, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -185,10 +185,7 @@ Connection_options get_connection_options(
 
   // The ssh has to be set as the last step cause we need the host and port to
   // be set, otherwise we can't use the tunneling.
-  auto ssh_data = get_ssh_options(instance_def);
-  // don't use has_data here as there's no remote_host yet in the
-  // ssh_connection_options, those will be set inside of the set_ssh_options.
-  if (ssh_data.has_host()) ret_val.set_ssh_options(ssh_data);
+  ret_val.set_ssh_options(get_ssh_options(instance_def));
 
   for (const auto &warning : ret_val.get_warnings())
     mysqlsh::current_console()->print_warning(warning);
@@ -220,12 +217,17 @@ mysqlshdk::ssh::Ssh_connection_options get_ssh_options(
   shcore::Argument_map connection_map(*instance_def);
   mysqlshdk::ssh::Ssh_connection_options ssh_config;
 
-  for (auto &option : *instance_def) {
+  // first we need to find the SSH URI, so it doesn't get overwritten by the
+  // second loop
+  for (const auto &option : *instance_def) {
     if (ssh_config.compare(option.first, mysqlshdk::db::kSsh) == 0) {
       ssh_config = mysqlshdk::ssh::Ssh_connection_options(
           connection_map.string_at(option.first));
-    } else if (ssh_config.compare(option.first,
-                                  mysqlshdk::db::kSshConfigFile) == 0) {
+    }
+  }
+
+  for (const auto &option : *instance_def) {
+    if (ssh_config.compare(option.first, mysqlshdk::db::kSshConfigFile) == 0) {
       ssh_config.set_config_file(connection_map.string_at(option.first));
     } else if (ssh_config.compare(option.first,
                                   mysqlshdk::db::kSshIdentityFile) == 0) {
@@ -519,9 +521,10 @@ std::shared_ptr<mysqlshdk::db::ISession> establish_session(
 
     copy.set_default_data();
 
-    auto &ssh = copy.get_ssh_options_handle();
-    if (ssh.has_data()) {
-      mysqlshdk::ssh::current_ssh_manager()->create_tunnel(&ssh);
+    mysqlshdk::ssh::Ssh_tunnel tunnel;
+
+    if (auto &ssh = copy.get_ssh_options(); ssh.has_data()) {
+      tunnel = mysqlshdk::ssh::current_ssh_manager()->create_tunnel(&ssh);
     }
 
     // TODO(alfredo) - password storage for MFA needs to be figured out
@@ -637,9 +640,9 @@ Connection_options get_classic_connection_options(
 
       co.set_port(row->get_int(0));
 
-      // if we're here, we clear up local port, so establish session will find
-      // the correct ssh tunnel or make a new one
-      co.get_ssh_options_handle().clear_local_port();
+      // if we're here, we clear up local port to make sure that SSH manager
+      // finds the correct SSH tunnel or makes a new one
+      co.get_ssh_options().clear_local_port();
     } else {
       // if we're here then socket was used
       const auto result =
diff --git a/modules/util/common/common_options.cc b/modules/util/common/common_options.cc
index fa3dee6269..c28f7a57e6 100644
--- a/modules/util/common/common_options.cc
+++ b/modules/util/common/common_options.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2024, 2025 Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -119,7 +119,7 @@ void Common_options::set_session(
       m_connection_options.remove(mysqlshdk::db::kLocalInfile);
     }
 
-    m_connection_options.set(mysqlshdk::db::kLocalInfile, "true");
+    m_connection_options.set(mysqlshdk::db::kLocalInfile, "1");
   }
 }
 
diff --git a/modules/util/common/dump/constants.h b/modules/util/common/dump/constants.h
index dd1d5e080f..4c93edf37a 100644
--- a/modules/util/common/dump/constants.h
+++ b/modules/util/common/dump/constants.h
@@ -52,6 +52,7 @@ constexpr inline std::array k_mhs_excluded_users = {
     "ociadmin",
     "ocidbm",
     "ocimonitor",
+    "ocirest",
     "ocirpl",
     "oracle-cloud-agent",
     "rrhhuser",
diff --git a/modules/util/dump/compatibility.cc b/modules/util/dump/compatibility.cc
index 3041d5e9d1..f38b4b0e60 100644
--- a/modules/util/dump/compatibility.cc
+++ b/modules/util/dump/compatibility.cc
@@ -78,6 +78,7 @@ const std::set<std::string> k_mysqlaas_allowed_privileges = {
     "FLUSH_STATUS",
     "FLUSH_TABLES",
     "FLUSH_USER_RESOURCES",
+    "OPTION_TRACKER_OBSERVER",
     "REPLICATION_APPLIER",
     "ROLE_ADMIN",
     "SHOW_ROUTINE",
diff --git a/modules/util/dump/dumper.cc b/modules/util/dump/dumper.cc
index 0d623e34da..9c09b95de6 100644
--- a/modules/util/dump/dumper.cc
+++ b/modules/util/dump/dumper.cc
@@ -842,7 +842,7 @@ class Dumper::Table_worker final {
       const Table_data_task &table,
       std::vector<Dump_writer::Encoding_type> *out_pre_encoded_columns) const {
     const auto base64 = m_dumper->m_options.use_base64();
-    std::string query = "SELECT SQL_NO_CACHE ";
+    std::string query = "SELECT " + m_dumper->optimizer_hints(table.info);
 
     for (const auto &column : table.info->columns) {
       if (column->csv_unsafe) {
@@ -1273,7 +1273,8 @@ class Dumper::Table_worker final {
     const auto row_count = [&info, &comment, this](const auto begin,
                                                    const auto end) {
       return row_count_from_explain(
-          query("EXPLAIN FORMAT=JSON SELECT COUNT(*) FROM " +
+          query("EXPLAIN FORMAT=JSON SELECT " +
+                m_dumper->optimizer_hints(info.table->info) + "COUNT(*) FROM " +
                 info.table->quoted_name + info.partition +
                 where(*info.table, between(info, begin, end)) + info.order_by +
                 comment)
@@ -1464,8 +1465,9 @@ class Dumper::Table_worker final {
         shcore::str_join(columns.begin() + info.index_column, columns.end(),
                          ",", [](const auto &c) { return c->quoted_name; });
 
-    const auto select = "SELECT SQL_NO_CACHE " + index + " FROM " +
-                        info.table->quoted_name + info.partition + " ";
+    const auto select =
+        "SELECT " + m_dumper->optimizer_hints(info.table->info) + index +
+        " FROM " + info.table->quoted_name + info.partition + " ";
     const auto order_by_and_limit = info.order_by + " LIMIT " +
                                     std::to_string(info.rows_per_chunk - 1) +
                                     ",2 ";
@@ -1514,7 +1516,7 @@ class Dumper::Table_worker final {
       return info.row_count / info.rows_per_chunk + 1;
     }
 
-    const auto sql = "SELECT SQL_NO_CACHE " +
+    const auto sql = "SELECT " + m_dumper->optimizer_hints(info.table->info) +
                      info.table->index.info->columns_sql() + " FROM " +
                      info.table->quoted_name + info.partition + where(info);
 
@@ -4763,21 +4765,33 @@ void Dumper::fetch_server_information() {
   DBUG_EXECUTE_IF("dumper_binlog_disabled", { m_binlog_enabled = false; });
   DBUG_EXECUTE_IF("dumper_gtid_disabled", { m_gtid_enabled = false; });
 
-  // given that minor versions may introduce breaking changes, we disallow
-  // dumps starting with the next minor version (8.0.x -> 8.1.0, 8.4.0 -> 8.5.0
-  // -> does not exist, but handles also 9.0.0)
-  const auto unsupported_version =
-      Version{k_shell_version.get_major(), k_shell_version.get_minor() + 1, 0};
-
-  DBUG_EXECUTE_IF("dumper_unsupported_server_version",
-                  { m_server_version.number = unsupported_version; });
+  // BUG#37866205 disallow dumps from server with a greater major version
+  DBUG_EXECUTE_IF("dumper_unsupported_server_version", {
+    m_server_version.number = Version(k_shell_version.get_major() + 1, 0, 0);
+  });
 
-  if (m_server_version.number >= unsupported_version) {
+  if (m_server_version.number.get_major() > k_shell_version.get_major()) {
     throw std::runtime_error(
         shcore::str_format("Unsupported MySQL Server %s detected, please "
                            "upgrade the MySQL Shell first",
                            m_server_version.number.get_base().c_str()));
   }
+
+  // BUG#36701854 given that minor versions may introduce breaking changes, we
+  // warn if server has a greater minor version (8.0.x -> 8.1.0, 8.4.0 -> 8.5.0
+  // -> does not exist, but handles also 9.0.0)
+  const auto newer_version =
+      Version{k_shell_version.get_major(), k_shell_version.get_minor() + 1, 0};
+
+  DBUG_EXECUTE_IF("dumper_newer_server_version",
+                  { m_server_version.number = newer_version; });
+
+  if (m_server_version.number >= newer_version) {
+    current_console()->print_warning(shcore::str_format(
+        "MySQL Server %s detected, which is newer than the MySQL Shell. Please "
+        "upgrade the MySQL Shell if dump or load operation fails.",
+        m_server_version.number.get_base().c_str()));
+  }
 }
 
 issues::Status_set Dumper::check_for_upgrade_errors() const {
@@ -4909,5 +4923,26 @@ common::Binlog Dumper::binlog(
   return common::binlog(session, m_server_version, true);
 }
 
+std::string Dumper::optimizer_hints(const Instance_cache::Table *info) const {
+  if (!m_server_version.is_8_0) {
+    return "SQL_NO_CACHE ";
+  } else if ("RAPID" == info->secondary_engine) {
+    // disable off-loading to heatwave
+    std::string hint = "/*+ SET_VAR(use_secondary_engine='OFF') ";
+
+    // disable hypergraph optimizer as a workaround for BUG#35443375
+    if (const auto version = m_server_version.number.numeric();
+        version >= 80400 && version < 90000) {
+      hint += "SET_VAR(optimizer_switch='hypergraph_optimizer=OFF') ";
+    }
+
+    hint += "*/ ";
+
+    return hint;
+  } else {
+    return {};
+  }
+}
+
 }  // namespace dump
 }  // namespace mysqlsh
diff --git a/modules/util/dump/dumper.h b/modules/util/dump/dumper.h
index 3633444ea3..737edaf2e6 100644
--- a/modules/util/dump/dumper.h
+++ b/modules/util/dump/dumper.h
@@ -470,6 +470,8 @@ class Dumper {
   common::Binlog binlog(
       const std::shared_ptr<mysqlshdk::db::ISession> &session) const;
 
+  std::string optimizer_hints(const Instance_cache::Table *info) const;
+
   // session
   std::shared_ptr<mysqlshdk::db::ISession> m_session;
 #ifndef NDEBUG
diff --git a/modules/util/dump/instance_cache.cc b/modules/util/dump/instance_cache.cc
index cb33c0f4a1..5179036188 100644
--- a/modules/util/dump/instance_cache.cc
+++ b/modules/util/dump/instance_cache.cc
@@ -30,6 +30,7 @@
 #include <algorithm>
 #include <iterator>
 #include <stdexcept>
+#include <string_view>
 #include <utility>
 
 #include "mysqlshdk/include/shellcore/console.h"
@@ -408,39 +409,55 @@ void Instance_cache_builder::filter_tables() {
   info.table_name = "tables";
   info.where = m_query_helper.table_filter(schema_column, table_column);
 
-  iterate_schemas(
-      info, [this](const std::string &, Instance_cache::Schema *schema,
-                   const mysqlshdk::db::IRow *row) {
-        const auto table_type = row->get_string(2);  // TABLE_TYPE
+  iterate_schemas(info, [this](const std::string &,
+                               Instance_cache::Schema *schema,
+                               const mysqlshdk::db::IRow *row) {
+    const auto table_type = row->get_string(2);  // TABLE_TYPE
 
-        if ("SYSTEM VIEW" == table_type) {
-          return;
-        }
+    if ("SYSTEM VIEW" == table_type) {
+      return;
+    }
 
-        const auto table_name = row->get_string(1);  // TABLE_NAME
-        const auto is_table = "BASE TABLE" == table_type;
-        Instance_cache::Table &target =
-            is_table ? schema->tables[table_name] : schema->views[table_name];
+    const auto table_name = row->get_string(1);  // TABLE_NAME
+    const auto is_table = "BASE TABLE" == table_type;
+    Instance_cache::Table &target =
+        is_table ? schema->tables[table_name] : schema->views[table_name];
 
-        target.row_count = row->get_uint(3, 0);           // TABLE_ROWS
-        target.average_row_length = row->get_uint(4, 0);  // AVG_ROW_LENGTH
-        target.engine = row->get_string(5, "");           // ENGINE
-        target.create_options = row->get_string(6, "");   // CREATE_OPTIONS
-        target.comment = row->get_string(7, "");          // TABLE_COMMENT
+    target.row_count = row->get_uint(3, 0);           // TABLE_ROWS
+    target.average_row_length = row->get_uint(4, 0);  // AVG_ROW_LENGTH
+    target.engine = row->get_string(5, "");           // ENGINE
+    target.create_options = row->get_string(6, "");   // CREATE_OPTIONS
+    target.comment = row->get_string(7, "");          // TABLE_COMMENT
 
-        if (is_table) {
-          set_has_tables();
+    if (is_table) {
+      set_has_tables();
 
-          ++m_cache.filtered.tables;
+      ++m_cache.filtered.tables;
 
-          DBUG_EXECUTE_IF("dumper_average_row_length_0",
-                          { target.average_row_length = 0; });
-        } else {
-          set_has_views();
+      DBUG_EXECUTE_IF("dumper_average_row_length_0",
+                      { target.average_row_length = 0; });
+    } else {
+      set_has_views();
 
-          ++m_cache.filtered.views;
+      ++m_cache.filtered.views;
+    }
+
+    if (!target.create_options.empty()) {
+      static constexpr std::string_view k_secondary_engine =
+          "SECONDARY_ENGINE=\"";
+
+      if (auto pos = target.create_options.find(k_secondary_engine);
+          std::string::npos != pos) {
+        pos += k_secondary_engine.length();
+
+        if (const auto end = target.create_options.find('"', pos);
+            std::string::npos != end) {
+          target.secondary_engine = shcore::str_upper(
+              std::string_view{target.create_options}.substr(pos, end - pos));
         }
-      });
+      }
+    }
+  });
 
   // the total number of tables and views within the filtered schemas
   m_cache.total.tables = count(info, "'BASE TABLE'=TABLE_TYPE");
@@ -693,11 +710,11 @@ void Instance_cache_builder::fetch_table_indexes() {
   info.table_column = "TABLE_NAME";
   info.extra_columns = {
       "INDEX_NAME",   // can be NULL in 8.0
-      "COLUMN_NAME",  // can be NULL in 8.0
+      "COLUMN_NAME",  // can be NULL
       "SEQ_IN_INDEX"  // NOT NULL
   };
   info.table_name = "statistics";
-  info.where = "COLUMN_NAME IS NOT NULL AND NON_UNIQUE=0";
+  info.where = "NON_UNIQUE=0";
 
   constexpr std::string_view k_primary_index = "PRIMARY";
   struct Index_info {
@@ -710,38 +727,42 @@ void Instance_cache_builder::fetch_table_indexes() {
       std::unordered_map<std::string, std::map<std::string, Index_info>>>
       indexes;
 
-  iterate_tables(
-      info,
-      [&indexes](const std::string &schema_name, const std::string &table_name,
-                 Instance_cache::Table *t, const mysqlshdk::db::IRow *row) {
-        // INDEX_NAME can be NULL in 8.0, as per output of 'SHOW COLUMNS', but
-        // it's not likely, as it's NOT NULL in definition of mysql.indexes
-        // hidden table
-        //
-        // NULL values in COLUMN_NAME are filtered out
-        const auto column =
-            std::find_if(t->all_columns.begin(), t->all_columns.end(),
-                         [name = row->get_string(3)](const auto &c) {
-                           return name == c.name;
-                         });  // COLUMN_NAME
-
-        // column will not be found if user is missing SELECT privilege and it
-        // was not possible to fetch column information
-        if (t->all_columns.end() != column) {
-          auto &index_info = indexes[schema_name][table_name]
-                                    [row->get_string(2, {})];  // INDEX_NAME
-          const auto seq = row->get_uint(4);                   // SEQ_IN_INDEX
-
-          // SEQ_IN_INDEX is 1-based
-          if (seq > index_info.columns.size()) {
-            index_info.columns.resize(seq);
-          }
+  iterate_tables(info, [&indexes](const std::string &schema_name,
+                                  const std::string &table_name,
+                                  Instance_cache::Table *t,
+                                  const mysqlshdk::db::IRow *row) {
+    // INDEX_NAME can be NULL in 8.0, as per output of 'SHOW COLUMNS', but
+    // it's not likely, as it's NOT NULL in definition of mysql.indexes
+    // hidden table
+
+    // COLUMN_NAME column is NULL if this is a functional key
+    Instance_cache::Column *column_ptr = nullptr;
+
+    if (!row->is_null(3)) {
+      const auto column =
+          std::find_if(t->all_columns.begin(), t->all_columns.end(),
+                       [name = row->get_string(3)](const auto &c) {
+                         return name == c.name;
+                       });  // COLUMN_NAME
+
+      // column will not be found if user is missing SELECT privilege and it
+      // was not possible to fetch column information
+      if (t->all_columns.end() != column) {
+        column_ptr = &(*column);
+      }
+    }
 
-          index_info.columns[seq - 1] = &(*column);
-        } else {
-          assert(t->all_columns.empty());
-        }
-      });
+    auto &index_info =
+        indexes[schema_name][table_name][row->get_string(2, {})];  // INDEX_NAME
+    const auto seq = row->get_uint(4);  // SEQ_IN_INDEX
+
+    // SEQ_IN_INDEX is 1-based
+    if (seq > index_info.columns.size()) {
+      index_info.columns.resize(seq);
+    }
+
+    index_info.columns[seq - 1] = column_ptr;
+  });
 
   for (const auto &schema : indexes) {
     for (const auto &table : schema.second) {
@@ -750,21 +771,30 @@ void Instance_cache_builder::fetch_table_indexes() {
       for (const auto &index : table.second) {
         Instance_cache::Index new_index;
         bool nullable = false;
+        bool add_index = true;
 
         for (const auto &column : index.second.columns) {
+          if (!column) {
+            // skip this index
+            add_index = false;
+            break;
+          }
+
           new_index.add_column(column);
           nullable |= column->nullable;
         }
 
-        auto ptr =
-            &t.indexes.emplace(index.first, std::move(new_index)).first->second;
+        if (add_index) {
+          auto ptr = &t.indexes.emplace(index.first, std::move(new_index))
+                          .first->second;
 
-        if (k_primary_index == index.first) {
-          t.primary_key = ptr;
-        } else if (!nullable) {
-          t.primary_key_equivalents.emplace_back(ptr);
-        } else {
-          t.unique_keys.emplace_back(ptr);
+          if (k_primary_index == index.first) {
+            t.primary_key = ptr;
+          } else if (!nullable) {
+            t.primary_key_equivalents.emplace_back(ptr);
+          } else {
+            t.unique_keys.emplace_back(ptr);
+          }
         }
       }
     }
diff --git a/modules/util/dump/instance_cache.h b/modules/util/dump/instance_cache.h
index 46a721fed1..91da04e4bf 100644
--- a/modules/util/dump/instance_cache.h
+++ b/modules/util/dump/instance_cache.h
@@ -107,6 +107,8 @@ struct Instance_cache {
     uint64_t row_count = 0;
     uint64_t average_row_length = 0;
     std::string engine;
+    // upper case name of the secondary engine
+    std::string secondary_engine;
     std::string create_options;
     std::string comment;
     std::unordered_map<std::string, Index> indexes;
diff --git a/modules/util/dump/schema_dumper.cc b/modules/util/dump/schema_dumper.cc
index b5321f94d5..6460a6df85 100644
--- a/modules/util/dump/schema_dumper.cc
+++ b/modules/util/dump/schema_dumper.cc
@@ -1303,6 +1303,7 @@ std::vector<Schema_dumper::Issue> Schema_dumper::get_table_structure(
   bool has_pk = false;
   bool has_my_row_id = false;
   bool has_auto_increment = false;
+  bool has_partitions = false;
   const auto my_row_id = "my_row_id";
   const std::string auto_increment = "auto_increment";
 
@@ -1695,12 +1696,25 @@ std::vector<Schema_dumper::Issue> Schema_dumper::get_table_structure(
     }
   }
 
+  if (opt_create_invisible_pks) {
+    if (m_cache) {
+      has_partitions =
+          !m_cache->schemas.at(db).tables.at(table).partitions.empty();
+    } else {
+      result = query_log_and_throw(shcore::sqlformat(
+          "SELECT COUNT(*) FROM information_schema.partitions WHERE "
+          "TABLE_SCHEMA=? AND TABLE_NAME=? AND PARTITION_NAME IS NOT NULL",
+          db, table));
+      has_partitions = result->fetch_one_or_throw()->get_uint(0);
+    }
+  }
+
   if (!has_pk) {
     const auto prefix =
         "Table " + quote(db, table) + " does not have a Primary Key, ";
 
     if (opt_create_invisible_pks) {
-      if (has_my_row_id || has_auto_increment) {
+      if (has_my_row_id || has_auto_increment || has_partitions) {
         if (has_my_row_id) {
           res.emplace_back(prefix +
                                "this cannot be fixed automatically because the "
@@ -1716,6 +1730,13 @@ std::vector<Schema_dumper::Issue> Schema_dumper::get_table_structure(
                   "column with 'AUTO_INCREMENT' attribute",
               Issue::Status::FIX_MANUALLY);
         }
+
+        if (has_partitions) {
+          res.emplace_back(prefix +
+                               "this cannot be fixed automatically because the "
+                               "table is partitioned",
+                           Issue::Status::FIX_MANUALLY);
+        }
       } else {
         res.emplace_back(prefix + "this will be fixed when the dump is loaded",
                          Issue::Status::FIXED_BY_CREATE_INVISIBLE_PKS);
@@ -2564,16 +2585,7 @@ Schema_dumper::Schema_dumper(
     const std::vector<std::string> &mysqlaas_supported_charsets)
     : m_mysql(mysql),
       m_mysqlaas_supported_charsets(mysqlaas_supported_charsets),
-      default_charset(MYSQL_UNIVERSAL_CLIENT_CHARSET) {
-  std::time_t t = std::time(nullptr);
-  m_dump_info = ", server " +
-                m_mysql->get_connection_options().as_uri(
-                    mysqlshdk::db::uri::formats::only_transport()) +
-                ", MySQL " + m_mysql->get_server_version().get_full() + " at " +
-                shcore::lexical_cast<std::string>(
-                    std::put_time(std::localtime(&t), "%c %Z")) +
-                "\n";
-}
+      default_charset(MYSQL_UNIVERSAL_CLIENT_CHARSET) {}
 
 void Schema_dumper::dump_all_tablespaces_ddl(IFile *file) {
   dump_all_tablespaces(file);
@@ -3562,7 +3574,7 @@ Schema_dumper::preprocess_users_script(
                         const std::string & /*column_list*/,
                         const std::string &object_type,
                         const std::string &priv_level) {
-                if (object_type != "" &&
+                if (!object_type.empty() &&
                     !shcore::str_caseeq(object_type, "TABLE"))
                   return true;
                 return !strip_privilege_cb(priv_type, priv_level);
diff --git a/modules/util/dump/schema_dumper.h b/modules/util/dump/schema_dumper.h
index affa017d1d..44941a0bae 100644
--- a/modules/util/dump/schema_dumper.h
+++ b/modules/util/dump/schema_dumper.h
@@ -238,8 +238,6 @@ class Schema_dumper {
   /* have we seen any VIEWs during table scanning? */
   bool seen_views = false;
 
-  std::string m_dump_info;
-
   const Instance_cache *m_cache = nullptr;
   const Filtering_options *m_filters = nullptr;
 
diff --git a/modules/util/import_table/load_data.cc b/modules/util/import_table/load_data.cc
index 0d718a6295..2205c6156e 100644
--- a/modules/util/import_table/load_data.cc
+++ b/modules/util/import_table/load_data.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -929,7 +929,7 @@ void Load_data_worker::execute(
         throw std::exception(e);
       }
 
-      const std::string task = fi.filehandler->filename();
+      const auto filename = fi.filehandler->filename();
       std::shared_ptr<mysqlshdk::db::IResult> load_result = nullptr;
       const std::string query_prefix = shcore::sqlformat(
           "LOAD DATA LOCAL INFILE ? ", fi.filehandler->full_path().masked());
@@ -992,7 +992,8 @@ void Load_data_worker::execute(
       {
         const char *mysql_info = session->get_mysql_info();
         const auto status =
-            worker_name + task + ": " + (mysql_info ? mysql_info : "ERROR") +
+            worker_name + filename + ": " +
+            (mysql_info ? mysql_info : "ERROR") +
             ((options.max_trx_size == 0 && max_trx_size == 0)
                  ? ""
                  : (fi.continuation
@@ -1022,49 +1023,54 @@ void Load_data_worker::execute(
         }
 
         if (warnings_num > 0) {
-          // show first k warnings, where k = warnings_to_show
-          constexpr int warnings_to_show = 5;
-          auto w = load_result->fetch_one_warning();
-
-          for (int i = 0; w && i < warnings_to_show;
-               w = load_result->fetch_one_warning(), i++) {
-            const std::string msg =
-                task + " error " + std::to_string(w->code) + ": " + w->msg;
-
-            switch (w->level) {
-              case mysqlshdk::db::Warning::Level::Error:
-                mysqlsh::current_console()->print_error(msg);
-                break;
-              case mysqlshdk::db::Warning::Level::Warn:
-                mysqlsh::current_console()->print_warning(msg);
-                break;
-              case mysqlshdk::db::Warning::Level::Note:
-                mysqlsh::current_console()->print_note(msg);
-                break;
+          // number of warnings to show
+          constexpr std::size_t k_warnings_to_show = 5;
+          std::size_t warnings_count = 0;
+
+          while (const auto w = load_result->fetch_one_warning()) {
+            const std::string msg = shcore::str_format(
+                "LOAD DATA error for `%s`.`%s` in file '%s', error %" PRIu32
+                ": %s",
+                m_opt.schema().c_str(), m_opt.table().c_str(), filename.c_str(),
+                w->code, w->msg.c_str());
+
+            if (warnings_count++ < k_warnings_to_show) {
+              // show first k warnings
+              switch (w->level) {
+                case mysqlshdk::db::Warning::Level::Error:
+                  mysqlsh::current_console()->print_error(msg);
+                  break;
+
+                case mysqlshdk::db::Warning::Level::Warn:
+                  mysqlsh::current_console()->print_warning(msg);
+                  break;
+
+                case mysqlshdk::db::Warning::Level::Note:
+                  mysqlsh::current_console()->print_note(msg);
+                  break;
+              }
+            } else {
+              // log remaining warnings
+              switch (w->level) {
+                case mysqlshdk::db::Warning::Level::Error:
+                  log_error("%s", msg.c_str());
+                  break;
+
+                case mysqlshdk::db::Warning::Level::Warn:
+                  log_warning("%s", msg.c_str());
+                  break;
+
+                case mysqlshdk::db::Warning::Level::Note:
+                  log_info("%s", msg.c_str());
+                  break;
+              }
             }
           }
 
-          // log remaining warnings
-          size_t remaining_warnings_count = 0;
-          for (; w; w = load_result->fetch_one_warning()) {
-            remaining_warnings_count++;
-            const std::string msg =
-                task + " error " + std::to_string(w->code) + ": " + w->msg;
-
-            switch (w->level) {
-              case mysqlshdk::db::Warning::Level::Error:
-                log_error("%s", msg.c_str());
-                break;
-              case mysqlshdk::db::Warning::Level::Warn:
-                log_warning("%s", msg.c_str());
-                break;
-              case mysqlshdk::db::Warning::Level::Note:
-                log_info("%s", msg.c_str());
-                break;
-            }
-          }
+          if (warnings_count > k_warnings_to_show) {
+            const auto remaining_warnings_count =
+                warnings_count - k_warnings_to_show;
 
-          if (remaining_warnings_count > 0) {
             mysqlsh::current_console()->print_info(
                 "Check mysqlsh.log for " +
                 std::to_string(remaining_warnings_count) + " more warning" +
diff --git a/modules/util/load/dump_loader.cc b/modules/util/load/dump_loader.cc
index 7404016a52..8e256ad680 100644
--- a/modules/util/load/dump_loader.cc
+++ b/modules/util/load/dump_loader.cc
@@ -31,6 +31,7 @@
 #include <array>
 #include <cassert>
 #include <condition_variable>
+#include <cstdint>
 #include <functional>
 #include <iterator>
 #include <list>
@@ -99,6 +100,7 @@ namespace {
 
 using Session_ptr = std::shared_ptr<mysqlshdk::db::mysql::Session>;
 using Reconnect = Dump_loader::Reconnect;
+using dump::Schema_dumper;
 
 constexpr auto k_temp_table_comment =
     "mysqlsh-tmp-218ffeec-b67b-4886-9a8c-d7812a1f93eb";
@@ -342,6 +344,11 @@ void execute_script(
   });
 }
 
+void drop_account(const Session_ptr &session, const std::string &account) {
+  const auto drop = "DROP USER IF EXISTS " + account;
+  execute_statement(session, drop, "While dropping the account " + account);
+}
+
 std::string format_table(std::string_view schema, std::string_view table,
                          std::string_view partition, ssize_t chunk) {
   std::string result = "`";
@@ -608,7 +615,7 @@ class Dump_loader::Bulk_load_support {
   std::size_t compatible_tables() const noexcept { return m_compatible_tables; }
 
   void on_bulk_load_start(Worker *worker, Worker::Bulk_load_task *task) {
-    {
+    if (worker->thread_id()) {
       std::lock_guard lock{m_monitored_threads_mutex};
 
       // we want to avoid bloating the progress file with progress update
@@ -1933,28 +1940,31 @@ void Dump_loader::Worker::connect() {
 
 uint64_t Dump_loader::Worker::current_thread_id() const {
   const auto query = [this](std::string_view q) {
-    // no reconnection - session setup step
-    return sql::query(session(), q)->fetch_one_or_throw()->get_uint(0);
+    try {
+      // no reconnection - session setup step
+      const auto result = sql::query(session(), q);
+
+      if (const auto row = result->fetch_one()) {
+        return row->get_uint(0);
+      } else {
+        throw std::runtime_error("Query returned an empty set");
+      }
+    } catch (const std::exception &e) {
+      log_warning("Unable to fetch thread ID for connection ID '%" PRIu64
+                  "': %s",
+                  m_connection_id, e.what());
+      return UINT64_C(0);
+    }
   };
 
   if (session()->get_server_version() >= Version(8, 0, 16)) {
     return query("SELECT PS_CURRENT_THREAD_ID()");
   }
 
-  try {
-    return query(
-        "SELECT THREAD_ID FROM performance_schema.threads WHERE "
-        "PROCESSLIST_ID=" +
-        std::to_string(connection_id()));
-  } catch (const mysqlshdk::db::Error &e) {
-    if (ER_TABLEACCESS_DENIED_ERROR != e.code()) {
-      throw;
-    }
-  }
-
-  log_warning("Unable to fetch thread ID for connection ID: %" PRIu64,
-              m_connection_id);
-  return 0;
+  return query(
+      "SELECT THREAD_ID FROM performance_schema.threads WHERE "
+      "PROCESSLIST_ID=" +
+      std::to_string(connection_id()));
 }
 
 void Dump_loader::Worker::schedule(std::unique_ptr<Task> task) {
@@ -2928,21 +2938,21 @@ void Dump_loader::show_summary() {
 
   if (m_options.load_users()) {
     std::string msg =
-        std::to_string(m_loaded_accounts) + " accounts were loaded";
+        std::to_string(m_users.loaded_accounts()) + " accounts were loaded";
 
-    if (m_ignored_plugin_errors) {
-      msg += ", " + std::to_string(m_ignored_plugin_errors) +
+    if (m_users.ignored_plugin_errors) {
+      msg += ", " + std::to_string(m_users.ignored_plugin_errors) +
              " accounts failed to load due to unsupported authentication "
              "plugin errors";
     }
 
-    if (m_ignored_grant_errors) {
-      msg += ", " + std::to_string(m_ignored_grant_errors) +
+    if (m_users.ignored_grant_errors) {
+      msg += ", " + std::to_string(m_users.ignored_grant_errors) +
              " GRANT statement errors were ignored";
     }
 
-    if (m_dropped_accounts) {
-      msg += ", " + std::to_string(m_dropped_accounts) +
+    if (m_users.dropped_accounts) {
+      msg += ", " + std::to_string(m_users.dropped_accounts) +
              " accounts were dropped due to GRANT statement errors";
     }
 
@@ -4217,6 +4227,10 @@ void Dump_loader::execute_table_ddl_tasks() {
 }
 
 void Dump_loader::execute_view_ddl_tasks() {
+  if (!m_options.load_ddl()) {
+    return;
+  }
+
   m_ddl_executed = 0;
   std::atomic<uint64_t> ddl_to_execute = 0;
   std::atomic<bool> all_tasks_scheduled = false;
@@ -4370,25 +4384,31 @@ void Dump_loader::execute_tasks(bool testing) {
       // NOTE: this assumes that all DDL files are already present
 
       if (m_options.load_ddl() || m_options.load_deferred_indexes()) {
+        // reads the SQL file, parses it, prepares for execution
+        if (!m_worker_interrupt.test()) read_users_sql();
+
         if (!m_worker_interrupt.test()) execute_drop_ddl_tasks();
 
+        if (!m_worker_interrupt.test()) drop_existing_accounts();
+
+        // user accounts have to be created first, because creating an object
+        // with the DEFINER clause requires that account to exist
+        if (!m_worker_interrupt.test()) create_accounts();
+
         if (!m_worker_interrupt.test()) execute_schema_ddl_tasks();
 
         // exec DDL for all tables in parallel (fetching DDL for
         // thousands of tables from remote storage can be slow)
         if (!m_worker_interrupt.test()) execute_table_ddl_tasks();
 
-        // users have to be loaded after all objects and view placeholders are
-        // created, because GRANTs on specific objects require the objects to
-        // exist
-        // users have to be loaded before view placeholders are replaced with
-        // views, because creating a view which uses another view with the
-        // DEFINER clause requires that user to exist
-        if (!m_worker_interrupt.test()) load_users();
+        // grants are applied before view placeholders are replaced with views,
+        // because creating a view which uses another view with the DEFINER
+        // clause requires that user to exist and to have appropriate privileges
+        // for that view
+        if (!m_worker_interrupt.test()) apply_grants();
 
         // exec DDL for all views after all tables and users are created
-        if (!m_worker_interrupt.test() && m_options.load_ddl())
-          execute_view_ddl_tasks();
+        if (!m_worker_interrupt.test()) execute_view_ddl_tasks();
       }
 
       if (!m_worker_interrupt.test() && !testing) setup_temp_tables();
@@ -5305,13 +5325,12 @@ Dump_loader::Task_ptr Dump_loader::checksum(
   return std::make_unique<Worker::Checksum_task>(data);
 }
 
-void Dump_loader::load_users() {
+void Dump_loader::read_users_sql() {
   if (!m_options.load_users()) {
     return;
   }
 
-  const auto stage =
-      m_progress_thread.start_stage("Executing user accounts SQL");
+  const auto stage = m_progress_thread.start_stage("Reading user accounts SQL");
   shcore::on_leave_scope finish_stage([stage]() { stage->finish(); });
 
   std::function<bool(const std::string &, const std::string &)>
@@ -5321,125 +5340,156 @@ void Dump_loader::load_users() {
     strip_revoked_privilege = filter_user_script_for_mds();
   }
 
-  const auto statements = dump::Schema_dumper::preprocess_users_script(
+  m_users.statements = Schema_dumper::preprocess_users_script(
       m_dump->users_script(),
       [this](const std::string &account) {
         return m_options.filters().users().is_included(account);
       },
       strip_revoked_privilege);
 
+  std::string new_stmt;
+
+  for (auto &group : m_users.statements) {
+    if (!group.account.empty()) {
+      m_users.all_accounts.emplace(group.account);
+    }
+
+    for (auto &stmt : group.statements) {
+      if (m_default_sql_transforms(stmt, &new_stmt)) {
+        stmt = std::move(new_stmt);
+        new_stmt.clear();
+      }
+    }
+  }
+}
+
+void Dump_loader::drop_existing_accounts() {
+  if (!m_options.load_users() || m_options.dry_run() ||
+      !m_options.drop_existing_objects()) {
+    return;
+  }
+
+  sql::ar::run(m_reconnect_callback, [this]() {
+    for (const auto &group : m_users.statements) {
+      if (Schema_dumper::User_statements::Type::CREATE_USER == group.type) {
+        drop_account(m_session, group.account);
+      }
+    }
+  });
+}
+
+void Dump_loader::create_accounts() {
+  if (!m_options.load_users()) {
+    return;
+  }
+
+  const auto stage = m_progress_thread.start_stage("Creating user accounts");
+  shcore::on_leave_scope finish_stage([stage]() { stage->finish(); });
+
   if (m_options.dry_run()) {
     return;
   }
 
-  const auto drop_account = [this](const std::string &account) {
-    const auto drop = "DROP USER IF EXISTS " + account;
-    execute_statement(m_session, drop, "While dropping the account " + account);
-  };
+  sql::ar::run(m_reconnect_callback, [this]() {
+    for (const auto &group : m_users.statements) {
+      if (Schema_dumper::User_statements::Type::CREATE_USER != group.type ||
+          m_users.ignored_accounts.count(group.account) > 0) {
+        continue;
+      }
+
+      for (const auto &stmt : group.statements) {
+        // check again if account is ignored to stop executing statements if one
+        // of them failed and account became ignored
+        if (stmt.empty() || m_users.ignored_accounts.count(group.account) > 0) {
+          continue;
+        }
+
+        try {
+          execute_statement(m_session, stmt, "While creating user accounts");
+        } catch (const mysqlshdk::db::Error &e) {
+          // BUG#36552764 - if target is MHS, ignore errors about missing
+          // plugins and continue
+          if (!m_options.is_mds() || ER_PLUGIN_IS_NOT_LOADED != e.code()) {
+            throw;
+          }
 
-  if (m_options.drop_existing_objects()) {
-    sql::ar::run(m_reconnect_callback, [&]() {
-      for (const auto &group : statements) {
-        if (dump::Schema_dumper::User_statements::Type::CREATE_USER ==
-            group.type) {
-          drop_account(group.account);
+          current_console()->print_warning(
+              "Due to the above error the account " + group.account +
+              " was not created, the load operation will continue.");
+          m_users.ignored_accounts.emplace(group.account);
+          ++m_users.ignored_plugin_errors;
         }
       }
-    });
+    }
+  });
+}
+
+void Dump_loader::apply_grants() {
+  if (!m_options.load_users()) {
+    return;
   }
 
-  std::unordered_set<std::string> all_accounts;
-  std::unordered_set<std::string> ignored_accounts;
+  const auto stage =
+      m_progress_thread.start_stage("Applying grants to user accounts");
+  shcore::on_leave_scope finish_stage([stage]() { stage->finish(); });
+
+  if (m_options.dry_run()) {
+    return;
+  }
 
   const auto reconnect = [&, this]() {
     m_reconnect_callback();
-
-    m_dropped_accounts = 0;
-    m_ignored_grant_errors = 0;
-    m_ignored_plugin_errors = 0;
-    all_accounts.clear();
-    ignored_accounts.clear();
+    m_users.ignored_grant_errors = 0;
   };
 
   const auto console = current_console();
 
-  sql::ar::run(reconnect, [&]() {
-    std::string new_stmt;
-
-    for (const auto &group : statements) {
-      if (!group.account.empty()) {
-        all_accounts.emplace(group.account);
-      }
-
-      if (ignored_accounts.count(group.account) > 0) {
+  sql::ar::run(reconnect, [this]() {
+    for (const auto &group : m_users.statements) {
+      if (Schema_dumper::User_statements::Type::CREATE_USER == group.type ||
+          m_users.ignored_accounts.count(group.account) > 0) {
         continue;
       }
 
-      for (std::string_view stmt : group.statements) {
-        // repeated to stop executing statements if one of the grants fails
-        if (ignored_accounts.count(group.account) > 0) {
-          continue;
-        }
-
-        if (m_default_sql_transforms(stmt, &new_stmt)) {
-          stmt = std::move(new_stmt);
-        }
-
-        if (stmt.empty()) {
+      for (const auto &stmt : group.statements) {
+        // check again if account is ignored to stop executing statements if one
+        // of them failed and account became ignored
+        if (stmt.empty() || m_users.ignored_accounts.count(group.account) > 0) {
           continue;
         }
 
         try {
           execute_statement(m_session, stmt,
-                            "While executing user accounts SQL");
+                            "While applying grants to user accounts");
         } catch (const mysqlshdk::db::Error &e) {
-          switch (group.type) {
-            case dump::Schema_dumper::User_statements::Type::CREATE_USER:
-              // BUG#36552764 - if target is MHS, ignore errors about missing
-              // plugins and continue
-              if (!m_options.is_mds() || ER_PLUGIN_IS_NOT_LOADED != e.code()) {
-                throw;
-              }
+          if (Schema_dumper::User_statements::Type::GRANT != group.type) {
+            throw;
+          }
 
-              console->print_warning(
+          switch (m_options.on_grant_errors()) {
+            case Load_dump_options::Handle_grant_errors::ABORT:
+              throw;
+
+            case Load_dump_options::Handle_grant_errors::DROP_ACCOUNT:
+              current_console()->print_note(
                   "Due to the above error the account " + group.account +
-                  " was not created, the load operation will continue.");
-              ignored_accounts.emplace(group.account);
-              ++m_ignored_plugin_errors;
+                  " was dropped, the load operation will continue.");
+              drop_account(m_session, group.account);
+              m_users.ignored_accounts.emplace(group.account);
+              ++m_users.dropped_accounts;
               break;
 
-            case dump::Schema_dumper::User_statements::Type::GRANT:
-              switch (m_options.on_grant_errors()) {
-                case Load_dump_options::Handle_grant_errors::ABORT:
-                  throw;
-
-                case Load_dump_options::Handle_grant_errors::DROP_ACCOUNT:
-                  console->print_note(
-                      "Due to the above error the account " + group.account +
-                      " was dropped, the load operation will continue.");
-                  ignored_accounts.emplace(group.account);
-                  drop_account(group.account);
-                  ++m_dropped_accounts;
-                  break;
-
-                case Load_dump_options::Handle_grant_errors::IGNORE:
-                  console->print_note(
-                      "The above error was ignored, the load operation will "
-                      "continue.");
-                  ++m_ignored_grant_errors;
-                  break;
-              }
+            case Load_dump_options::Handle_grant_errors::IGNORE:
+              current_console()->print_note(
+                  "The above error was ignored, the load operation will "
+                  "continue.");
+              ++m_users.ignored_grant_errors;
               break;
-
-            default:
-              throw;
           }
         }
       }
     }
   });
-
-  m_loaded_accounts = all_accounts.size() - ignored_accounts.size();
 }
 
 void Dump_loader::show_metadata(bool force) const {
diff --git a/modules/util/load/dump_loader.h b/modules/util/load/dump_loader.h
index dd485d0055..97461103d9 100644
--- a/modules/util/load/dump_loader.h
+++ b/modules/util/load/dump_loader.h
@@ -45,6 +45,7 @@
 
 #include "modules/util/dump/compatibility.h"
 #include "modules/util/dump/progress_thread.h"
+#include "modules/util/dump/schema_dumper.h"
 
 #include "modules/util/load/dump_reader.h"
 #include "modules/util/load/load_dump_options.h"
@@ -391,6 +392,7 @@ class Dump_loader {
 
     std::shared_ptr<mysqlshdk::db::mysql::Session> m_session;
     uint64_t m_connection_id = 0;
+    // will be set to 0 if it's not possible to fetch the thread ID
     uint64_t m_thread_id = 0;
     Reconnect m_reconnect_callback;
 
@@ -574,7 +576,13 @@ class Dump_loader {
 
   Task_ptr checksum(const dump::common::Checksums::Checksum_data *data) const;
 
-  void load_users();
+  void read_users_sql();
+
+  void drop_existing_accounts();
+
+  void create_accounts();
+
+  void apply_grants();
 
   bool bulk_load_supported(const Dump_reader::Table_chunk &chunk) const;
 
@@ -689,6 +697,22 @@ class Dump_loader {
     std::vector<Container> m_tasks;
   };
 
+  struct User_accounts {
+    std::size_t loaded_accounts() const {
+      return all_accounts.size() - ignored_accounts.size();
+    }
+
+    // data
+    std::vector<dump::Schema_dumper::User_statements> statements;
+    std::unordered_set<std::string> all_accounts;
+    std::unordered_set<std::string> ignored_accounts;
+
+    // stats
+    std::size_t dropped_accounts = 0;
+    std::size_t ignored_grant_errors = 0;
+    std::size_t ignored_plugin_errors = 0;
+  };
+
   class Bulk_load_support;
 
   class Monitoring;
@@ -779,10 +803,7 @@ class Dump_loader {
   dump::Progress_thread::Stage *m_analyze_tables_stage = nullptr;
   dump::Progress_thread::Stage *m_checksum_tables_stage = nullptr;
 
-  std::size_t m_loaded_accounts = 0;
-  std::size_t m_dropped_accounts = 0;
-  std::size_t m_ignored_grant_errors = 0;
-  std::size_t m_ignored_plugin_errors = 0;
+  User_accounts m_users;
 
   std::size_t m_checksum_errors = 0;
 
diff --git a/modules/util/mod_util.cc b/modules/util/mod_util.cc
index 1f60dfb0be..8e8fe0f6f4 100644
--- a/modules/util/mod_util.cc
+++ b/modules/util/mod_util.cc
@@ -684,8 +684,8 @@ configuration file instead of the one at the default location.
 instead of the default one.
 @li <b>ociAuth</b>: string (default: not set) - Use the specified authentication
 method when connecting to the OCI. Allowed values: <b>api_key</b> (used when not
-explicitly set), <b>instance_principal</b>, <b>resource_principal</b>,
-<b>security_token</b>.)*");
+explicitly set), <b>instance_obo_user</b>, <b>instance_principal</b>,
+<b>resource_principal</b>, <b>security_token</b>.)*");
 
 REGISTER_HELP_DETAIL_TEXT(TOPIC_OCI_STORAGE_OPTIONS_DETAILS, R"*(
 If the <b>osBucketName</b> option is used, the specified OCI bucket is used as
@@ -703,6 +703,7 @@ The <b>ociAuth</b> option allows to specify the authentication method used when
 connecting to the OCI:
 
 @li <b>api_key</b> - API Key-Based Authentication
+@li <b>instance_obo_user</b> - Instance Principal Authentication with delegation token
 @li <b>instance_principal</b> - Instance Principal Authentication
 @li <b>resource_principal</b> - Resource Principal Authentication
 @li <b>security_token</b> - Session Token-Based Authentication
diff --git a/modules/util/upgrade_checker/upgrade_check_creators.cc b/modules/util/upgrade_checker/upgrade_check_creators.cc
index 9a19a30978..38cca07b6f 100644
--- a/modules/util/upgrade_checker/upgrade_check_creators.cc
+++ b/modules/util/upgrade_checker/upgrade_check_creators.cc
@@ -1593,6 +1593,16 @@ std::unique_ptr<Upgrade_check> get_foreign_key_references_check() {
       ids::k_foreign_key_references,
       std::vector<Check_query>{
           {
+/*
+ * query for finding foreign key constraint to non unique key
+ *
+ * Query works in three parts - first one (using REFERENTIAL_CONSTRAINTS and
+ * KEY_COLUMN_USAGE tables) constructs a list of constraints with fk definitions.
+ * Second part (using STATISTICS table) creates a list of sums of non unique
+ * keys that are join to the previous query using the fk definition.
+ * Last, the final part, produces from previous parts results a list of
+ * constraints that have any (non zero) non unique keys .
+ */
 R"(select 
   fk.constraint_schema, 
   fk.constraint_name,
@@ -1639,28 +1649,65 @@ from (select
       idx.non_unique_count > 0)",
                        Upgrade_issue::Object_type::FOREIGN_KEY},
                        {
-"SELECT constraint_schema, "
-        "name, "
-        "'', " // column field
-        "fk_definition, "
-        "col_list, "
-        "target_table, "
-        "'##fkToPartialKey'"
-  "FROM (SELECT rc.constraint_schema constraint_schema, "
-               "rc.constraint_name name, "
-               "rc.referenced_table_name target_table, "
-               "CONCAT(rc.constraint_schema,'.',rc.table_name,'(',GROUP_CONCAT(kc.column_name order by kc.ORDINAL_POSITION),')') fk_definition, "
-               "CONCAT(rc.constraint_schema,'.',rc.referenced_table_name,'(',GROUP_CONCAT(kc.referenced_column_name order by kc.ORDINAL_POSITION),')') col_list "
-        "FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc INNER JOIN "
-             "INFORMATION_SCHEMA.KEY_COLUMN_USAGE kc ON "
-             "rc.constraint_schema=kc.constraint_schema AND "
-             "rc.constraint_name=kc.constraint_name AND "
-             "<<schema_filter:rc.constraint_schema>> "
-        "GROUP BY "
-             "rc.constraint_name,rc.constraint_schema, rc.table_name, "
-             "rc.referenced_table_name) fk "
-        "WHERE fk.col_list NOT IN (SELECT CONCAT(table_schema,'.',table_name,'(',GROUP_CONCAT(column_name order by seq_in_index),')') col_list "
-        "FROM INFORMATION_SCHEMA.STATISTICS WHERE sub_part IS NULL AND <<schema_filter:table_schema>> GROUP BY table_schema, table_name, index_name);"
+/*
+ * query for finding foreign key constraint to partial keys
+ *
+ * Query works in three parts - first one (using REFERENTIAL_CONSTRAINTS and
+ * KEY_COLUMN_USAGE tables) constructs list of constraints with source and
+ * target (col_list) fk definitions.
+ * Second part (using STATISTICS table) constructs a list of known fk definitions.
+ * Last, final part tries to find target fk definitions from first part in
+ * the list from the second part - constraints with missing fk definitions in
+ * second part are the ones with partial keys.
+ */
+                         R"(SELECT
+  constraint_schema,
+  name,
+  '',
+  fk_definition,
+  col_list,
+  target_table,
+  '##fkToPartialKey'
+FROM (
+  SELECT
+    rc.constraint_schema constraint_schema,
+    rc.constraint_name name,
+    rc.referenced_table_name target_table,
+    CONCAT(kc.table_schema,'.',rc.table_name,'(',GROUP_CONCAT(kc.column_name order by kc.ORDINAL_POSITION),')') fk_definition,
+    CONCAT(kc.referenced_table_schema,'.',kc.referenced_table_name,'(',GROUP_CONCAT(kc.referenced_column_name order by kc.ORDINAL_POSITION),')') col_list
+  FROM
+    INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc
+    INNER JOIN
+      INFORMATION_SCHEMA.KEY_COLUMN_USAGE kc
+    ON
+      rc.constraint_schema = kc.constraint_schema AND
+      rc.constraint_name = kc.constraint_name AND
+      rc.table_name = kc.table_name AND
+      rc.referenced_table_name = kc.referenced_table_name AND
+      <<schema_filter:rc.constraint_schema>>
+
+    GROUP BY
+      rc.constraint_name,
+      rc.constraint_schema,
+      kc.table_schema,
+      kc.table_name,
+      kc.referenced_table_schema,
+      kc.referenced_table_name
+  ) fk
+WHERE
+  fk.col_list NOT IN (
+    SELECT
+      CONCAT(table_schema,'.',table_name,'(',GROUP_CONCAT(column_name order by seq_in_index),')') col_list
+    FROM
+      INFORMATION_SCHEMA.STATISTICS
+    WHERE
+      sub_part IS NULL AND
+      <<schema_filter:table_schema>>
+    GROUP BY
+      table_schema,
+      table_name,
+      index_name
+    ))"
                         ,Upgrade_issue::Object_type::FOREIGN_KEY
                        }},
       Upgrade_issue::WARNING);
diff --git a/mysql-secret-store/CMakeLists.txt b/mysql-secret-store/CMakeLists.txt
index d9cfd1c59f..ac30a28346 100644
--- a/mysql-secret-store/CMakeLists.txt
+++ b/mysql-secret-store/CMakeLists.txt
@@ -1,4 +1,4 @@
-# Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+# Copyright (c) 2018, 2025, Oracle and/or its affiliates.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License, version 2.0,
@@ -41,7 +41,6 @@ else()
   if (APPLE)
     add_subdirectory(keychain)
   else()
-    # secret-service is disabled, as secret-tool is not reliable
-    # add_subdirectory(secret-service)
+    add_subdirectory(secret-service)
   endif()
 endif()
diff --git a/mysql-secret-store/core/helper.cc b/mysql-secret-store/core/helper.cc
index dc04121664..5f1c2f617b 100644
--- a/mysql-secret-store/core/helper.cc
+++ b/mysql-secret-store/core/helper.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -33,13 +33,19 @@ Helper_exception get_helper_exception(Helper_exception_code code) {
   switch (code) {
     case Helper_exception_code::NO_SUCH_SECRET:
       return Helper_exception{"Could not find the secret"};
+
+    case Helper_exception_code::INVALID_SECRET:
+      return Helper_exception{"Secret contains a disallowed character"};
+
+    case Helper_exception_code::INVALID_SECRET_ID:
+      return Helper_exception{"Secret's ID contains a disallowed character"};
   }
 
   throw std::runtime_error{"Unknown exception code"};
 }
 
 bool Secret_id::operator==(const Secret_id &r) const noexcept {
-  return secret_type == r.secret_type && url == r.url;
+  return secret_type == r.secret_type && id == r.id;
 }
 
 bool Secret_id::operator!=(const Secret_id &r) const noexcept {
diff --git a/mysql-secret-store/core/json_converter.cc b/mysql-secret-store/core/json_converter.cc
index e84f338354..3633ce99e6 100644
--- a/mysql-secret-store/core/json_converter.cc
+++ b/mysql-secret-store/core/json_converter.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -30,7 +30,7 @@
 #include <rapidjson/stringbuffer.h>
 #include <rapidjson/writer.h>
 
-#include "mysqlshdk/libs/db/connection_options.h"
+#include <utility>
 
 using mysql::secret_store::common::Helper_exception;
 
@@ -43,6 +43,8 @@ namespace {
 
 constexpr auto k_secret = "Secret";
 constexpr auto k_secret_type = "SecretType";
+constexpr auto k_secret_id = "SecretID";
+// TODO(pawel): this is deprecated, remove it at some point
 constexpr auto k_server_url = "ServerURL";
 
 std::string to_string(rapidjson::Document *doc) {
@@ -62,6 +64,10 @@ rapidjson::Document parse(const std::string &json) {
                              rapidjson::GetParseError_En(doc.GetParseError())};
   }
 
+  if (!doc.IsObject()) {
+    std::runtime_error{"Failed to parse JSON: expected object"};
+  }
+
   return doc;
 }
 
@@ -75,50 +81,65 @@ rapidjson::Document to_object(
                 rapidjson::StringRef(secret_id.secret_type.c_str(),
                                      secret_id.secret_type.length()),
                 a);
+  doc.AddMember(
+      rapidjson::StringRef(k_secret_id),
+      rapidjson::StringRef(secret_id.id.c_str(), secret_id.id.length()), a);
   doc.AddMember(
       rapidjson::StringRef(k_server_url),
-      rapidjson::StringRef(secret_id.url.c_str(), secret_id.url.length()), a);
+      rapidjson::StringRef(secret_id.id.c_str(), secret_id.id.length()), a);
 
   return doc;
 }
 
-void validate(const rapidjson::Value &v,
-              const std::vector<std::string> &required) {
-  for (const auto &r : required) {
-    if (!v.HasMember(r.c_str())) {
-      throw Helper_exception{"\"" + r + "\" is missing"};
-    }
-    if (!v[r.c_str()].IsString()) {
-      throw Helper_exception{"\"" + r + "\" should be a string"};
-    }
+std::string required(const rapidjson::Value &object, const char *name) {
+  const auto it = object.FindMember(name);
+
+  if (object.MemberEnd() == it) {
+    throw Helper_exception{"\"" + std::string(name) + "\" is missing"};
   }
+
+  if (!it->value.IsString()) {
+    throw Helper_exception{"\"" + std::string(name) + "\" should be a string"};
+  }
+
+  return std::string{it->value.GetString(), it->value.GetStringLength()};
+}
+
+std::string to_secret_type(const rapidjson::Value &secret_type) {
+  return required(secret_type, k_secret_type);
 }
 
 common::Secret_id to_secret_id(const rapidjson::Value &secret_id) {
-  validate(secret_id, {k_secret_type, k_server_url});
-  return {secret_id[k_secret_type].GetString(),
-          secret_id[k_server_url].GetString()};
+  std::string id;
+
+  // we first check for the deprecated key, to have a meaningful message if the
+  // new key is missing
+  try {
+    id = required(secret_id, k_server_url);
+  } catch (...) {
+    id = required(secret_id, k_secret_id);
+  }
+
+  return {to_secret_type(secret_id), std::move(id)};
 }
 
 }  // namespace
 
 common::Secret to_secret(const std::string &secret) {
-  auto doc = parse(secret);
-  validate(doc, {k_secret});
-  if (doc.MemberCount() > 3) {
-    throw Helper_exception{"JSON object contains unknown member"};
-  }
-  return {doc[k_secret].GetString(), to_secret_id(doc)};
+  const auto doc = parse(secret);
+  return {required(doc, k_secret), to_secret_id(doc)};
 }
 
 common::Secret_id to_secret_id(const std::string &secret_id) {
-  auto doc = parse(secret_id);
-  if (doc.MemberCount() > 2) {
-    throw Helper_exception{"JSON object contains unknown member"};
-  }
+  const auto doc = parse(secret_id);
   return to_secret_id(doc);
 }
 
+std::string to_secret_type(const std::string &secret_type) {
+  const auto doc = parse(secret_type);
+  return to_secret_type(doc);
+}
+
 std::string to_string(const common::Secret &secret) {
   auto doc = to_object(secret.id);
   auto &allocator = doc.GetAllocator();
diff --git a/mysql-secret-store/core/json_converter.h b/mysql-secret-store/core/json_converter.h
index 4cbe9fe8ec..bffa5ddce9 100644
--- a/mysql-secret-store/core/json_converter.h
+++ b/mysql-secret-store/core/json_converter.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -40,6 +40,8 @@ common::Secret to_secret(const std::string &secret);
 
 common::Secret_id to_secret_id(const std::string &secret_id);
 
+std::string to_secret_type(const std::string &secret_type);
+
 std::string to_string(const common::Secret &secret);
 
 std::string to_string(const common::Secret_id &secret_id);
diff --git a/mysql-secret-store/core/list_command.cc b/mysql-secret-store/core/list_command.cc
index dcf66e4ccd..2716e73eaf 100644
--- a/mysql-secret-store/core/list_command.cc
+++ b/mysql-secret-store/core/list_command.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -25,6 +25,10 @@
 
 #include "mysql-secret-store/core/list_command.h"
 
+#include <optional>
+#include <sstream>
+#include <string>
+#include <utility>
 #include <vector>
 
 #include "mysql-secret-store/core/json_converter.h"
@@ -35,9 +39,20 @@ namespace core {
 
 std::string List_command::help() const { return "Lists the secrets."; }
 
-void List_command::execute(std::istream *, std::ostream *output) {
+void List_command::execute(std::istream *input, std::ostream *output) {
+  std::optional<std::string> secret_type;
+
+  {
+    std::stringstream buffer;
+    buffer << input->rdbuf();
+
+    if (const auto s = std::move(buffer).str(); !s.empty()) {
+      secret_type = converter::to_secret_type(s);
+    }
+  }
+
   std::vector<common::Secret_id> secrets;
-  helper()->list(&secrets);
+  helper()->list(&secrets, std::move(secret_type));
 
   *output << converter::to_string(secrets);
 }
diff --git a/mysql-secret-store/include/helper.h b/mysql-secret-store/include/helper.h
index 29428189f0..b30bd12b90 100644
--- a/mysql-secret-store/include/helper.h
+++ b/mysql-secret-store/include/helper.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024 Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025 Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -26,6 +26,7 @@
 #ifndef MYSQL_SECRET_STORE_INCLUDE_HELPER_H_
 #define MYSQL_SECRET_STORE_INCLUDE_HELPER_H_
 
+#include <optional>
 #include <stdexcept>
 #include <string>
 #include <vector>
@@ -49,7 +50,9 @@ class Helper_exception : public std::runtime_error {
  * Codes used to translate errors to Helper_exceptions.
  */
 enum class Helper_exception_code {
-  NO_SUCH_SECRET /**< Requested secret does not exist. */
+  NO_SUCH_SECRET,    /**< Requested secret does not exist. */
+  INVALID_SECRET,    /**< Secret contains disallowed character(s). */
+  INVALID_SECRET_ID, /**< Secret's ID contains disallowed character(s). */
 };
 
 /**
@@ -76,7 +79,7 @@ struct Secret_id {
   bool operator!=(const Secret_id &r) const noexcept;
 
   std::string secret_type; /**< Secret's type. */
-  std::string url;         /**< Secret's URL. */
+  std::string id;          /**< Secret's ID. */
 };
 
 /**
@@ -174,10 +177,12 @@ class Helper {
    * Retrieves all secrets stored by this secret store helper.
    *
    * @param secrets Retrieved IDs of stored secrets.
+   * @param secret_type If given, only secrets of this type are retrieved.
    *
    * @throws Helper_exception in case of an error.
    */
-  virtual void list(std::vector<Secret_id> *secrets) = 0;
+  virtual void list(std::vector<Secret_id> *secrets,
+                    std::optional<std::string> secret_type = {}) = 0;
 
  private:
   std::string m_name;
diff --git a/mysql-secret-store/include/mysql-secret-store/api.h b/mysql-secret-store/include/mysql-secret-store/api.h
index b77026cef0..d91b602444 100644
--- a/mysql-secret-store/include/mysql-secret-store/api.h
+++ b/mysql-secret-store/include/mysql-secret-store/api.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -28,21 +28,17 @@
 
 #include <functional>
 #include <memory>
+#include <optional>
 #include <string>
 #include <string_view>
 #include <vector>
 
+#include "mysql-secret-store/include/mysql-secret-store/secret_type.h"
+
 namespace mysql {
 namespace secret_store {
 namespace api {
 
-/**
- * Types of secrets.
- */
-enum class Secret_type {
-  PASSWORD /**< Secret is a password */
-};
-
 /**
  * Specification of a secret, uniquely identifies a secret.
  */
@@ -59,7 +55,7 @@ struct Secret_spec {
   bool operator!=(const Secret_spec &r) const noexcept;
 
   Secret_type type; /**< Secret's type. */
-  std::string url;  /**< Secret's URL. */
+  std::string id;   /**< Secret's ID. */
 };
 
 /**
@@ -177,10 +173,12 @@ class Helper_interface final {
    * helper.
    *
    * @param specs Retrieved list of secret specifications.
+   * @param type If given, only secrets of this type are retrieved.
    *
    * @returns true if list was successfully retrieved.
    */
-  bool list(std::vector<Secret_spec> *specs) const noexcept;
+  bool list(std::vector<Secret_spec> *specs,
+            std::optional<Secret_type> type = {}) const noexcept;
 
   /**
    * Provides error message of the previous operation (store/get/erase/list)
diff --git a/mysqlshdk/scripting/enable_x_protocol.js b/mysql-secret-store/include/mysql-secret-store/secret_type.h
similarity index 54%
rename from mysqlshdk/scripting/enable_x_protocol.js
rename to mysql-secret-store/include/mysql-secret-store/secret_type.h
index 1f6da7ab52..564e17b925 100644
--- a/mysqlshdk/scripting/enable_x_protocol.js
+++ b/mysql-secret-store/include/mysql-secret-store/secret_type.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -23,35 +23,23 @@
  * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
+#ifndef MYSQL_SECRET_STORE_INCLUDE_MYSQL_SECRET_STORE_SECRET_TYPE_H_
+#define MYSQL_SECRET_STORE_INCLUDE_MYSQL_SECRET_STORE_SECRET_TYPE_H_
 
-function enableXProtocol()
-{
-  // check if mysqlx installed
-  try
-  {
-    var mysqlx_port = session.runSql('select @@mysqlx_port').fetchOne();
-    print("enableXProtocol: X Protocol plugin is already enabled and listening for connections on port "+mysqlx_port[0]+"\n");
-    return 0;
-  }
-  catch (error)
-  {
-    if (error["code"] != 1193) // unknown system variable
-    {
-      print("enableXProtocol: Error checking for X Protocol plugin: "+error["message"]+"\n");
-      return 1;
-    }
-  }
+namespace mysql {
+namespace secret_store {
+namespace api {
 
-  print("enableXProtocol: Installing plugin mysqlx...\n");
-  // if not installed, then check OS
-  var os = session.runSql('select @@version_compile_os').fetchOne();
-  if (os[0] == "Win32" || os[0] == "Win64")
-  {
-    session.runSql("install plugin mysqlx soname 'mysqlx.dll';");
-  }
-  else
-  {
-    session.runSql("install plugin mysqlx soname 'mysqlx.so';")
-  }
-  print("enableXProtocol: done\n");
-}
+/**
+ * Types of secrets.
+ */
+enum class Secret_type {
+  PASSWORD, /**< Secret is a password, Secret's ID is expected to be a URL. */
+  GENERIC,  /**< Generic secret. */
+};
+
+}  // namespace api
+}  // namespace secret_store
+}  // namespace mysql
+
+#endif  // MYSQL_SECRET_STORE_INCLUDE_MYSQL_SECRET_STORE_SECRET_TYPE_H_
diff --git a/mysql-secret-store/keychain/macos_security_helper.cc b/mysql-secret-store/keychain/macos_security_helper.cc
index f22259b21f..878cd240eb 100644
--- a/mysql-secret-store/keychain/macos_security_helper.cc
+++ b/mysql-secret-store/keychain/macos_security_helper.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -25,6 +25,10 @@
 
 #include "mysql-secret-store/keychain/macos_security_helper.h"
 
+#include <string>
+#include <string_view>
+#include <utility>
+
 #include "mysqlshdk/libs/utils/utils_general.h"
 #include "mysqlshdk/libs/utils/utils_string.h"
 
@@ -38,6 +42,9 @@ namespace keychain {
 
 namespace {
 
+constexpr std::string_view k_mysqlsh_scheme = "mysqlsh:/";
+constexpr std::string_view k_password_type = "password";
+
 void handle_find_exception(const Helper_exception &ex) {
   if (std::string{ex.what()}.find("SecKeychainSearchCopyNext") !=
       std::string::npos) {
@@ -47,6 +54,10 @@ void handle_find_exception(const Helper_exception &ex) {
   }
 }
 
+bool validate_string(const std::string &str) {
+  return std::string::npos == str.find_first_of("\x00\x0A", 0, 2);
+}
+
 }  // namespace
 
 Macos_security_helper::Macos_security_helper()
@@ -56,6 +67,14 @@ Macos_security_helper::Macos_security_helper()
 void Macos_security_helper::check_requirements() { m_invoker.validate(); }
 
 void Macos_security_helper::store(const common::Secret &secret) {
+  if (!validate_string(secret.id.id)) {
+    throw get_helper_exception(Helper_exception_code::INVALID_SECRET_ID);
+  }
+
+  if (!validate_string(secret.secret)) {
+    throw get_helper_exception(Helper_exception_code::INVALID_SECRET);
+  }
+
   m_invoker.store(get_entry(secret.id), secret.secret);
 }
 
@@ -76,16 +95,31 @@ void Macos_security_helper::erase(const common::Secret_id &id) {
   }
 }
 
-void Macos_security_helper::list(std::vector<common::Secret_id> *secrets) {
-  for (const auto &entry : m_invoker.list()) {
-    std::string url;
-    if (!entry.account.empty() && !entry.service.empty()) {
-      url = entry.account + "@" + entry.service;
+void Macos_security_helper::list(std::vector<common::Secret_id> *secrets,
+                                 std::optional<std::string> secret_type) {
+  const std::string mysqlsh_scheme{k_mysqlsh_scheme};
+
+  for (auto &entry : m_invoker.list()) {
+    std::string id;
+
+    if (k_password_type == entry.type) {
+      if (!entry.account.empty() && !entry.service.empty()) {
+        id = entry.account + "@" + entry.service;
+      } else {
+        id = entry.account + entry.service;
+      }
     } else {
-      url = entry.account + entry.service;
+      if (entry.service != mysqlsh_scheme + entry.type) {
+        throw Helper_exception{"Invalid entry detected"};
+      }
+
+      id = std::move(entry.account);
     }
 
-    secrets->emplace_back(common::Secret_id{entry.type, url});
+    if (!secret_type.has_value() || *secret_type == entry.type) {
+      secrets->emplace_back(
+          common::Secret_id{std::move(entry.type), std::move(id)});
+    }
   }
 }
 
@@ -94,14 +128,20 @@ Security_invoker::Entry Macos_security_helper::get_entry(
   Security_invoker::Entry entry;
   entry.type = id.secret_type;
 
-  const auto at_position = id.url.find("@");
-
-  if (std::string::npos == at_position) {
-    entry.account = "";
-    entry.service = id.url;
+  if (k_password_type == id.secret_type) {
+    if (const auto at_position = id.id.find("@");
+        std::string::npos == at_position) {
+      entry.account = "";
+      entry.service = id.id;
+    } else {
+      entry.account = id.id.substr(0, at_position);
+      entry.service = id.id.substr(at_position + 1);
+    }
   } else {
-    entry.account = id.url.substr(0, at_position);
-    entry.service = id.url.substr(at_position + 1);
+    entry.account = id.id;
+    entry.service.reserve(k_mysqlsh_scheme.length() + id.secret_type.length());
+    entry.service = k_mysqlsh_scheme;
+    entry.service += id.secret_type;
   }
 
   return entry;
diff --git a/mysql-secret-store/keychain/macos_security_helper.h b/mysql-secret-store/keychain/macos_security_helper.h
index f4029bee2d..3720aa3910 100644
--- a/mysql-secret-store/keychain/macos_security_helper.h
+++ b/mysql-secret-store/keychain/macos_security_helper.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -49,7 +49,8 @@ class Macos_security_helper : public common::Helper {
 
   void erase(const common::Secret_id &) override;
 
-  void list(std::vector<common::Secret_id> *) override;
+  void list(std::vector<common::Secret_id> *,
+            std::optional<std::string>) override;
 
  private:
   Security_invoker::Entry get_entry(const common::Secret_id &);
diff --git a/mysql-secret-store/keychain/security_invoker.cc b/mysql-secret-store/keychain/security_invoker.cc
index cddeb9a97b..f4336d9701 100644
--- a/mysql-secret-store/keychain/security_invoker.cc
+++ b/mysql-secret-store/keychain/security_invoker.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -25,6 +25,8 @@
 
 #include "mysql-secret-store/keychain/security_invoker.h"
 
+#include <utility>
+
 #include "mysql-secret-store/include/helper.h"
 #include "mysqlshdk/libs/utils/process_launcher.h"
 #include "mysqlshdk/libs/utils/utils_general.h"
@@ -80,6 +82,7 @@ std::string hex2string(const std::string &in) {
 void Security_invoker::validate() { invoke({"help"}); }
 
 void Security_invoker::store(const Entry &entry, const std::string &secret) {
+  // NOTE: primary key of a generic password is account + service
   std::vector<std::string> args{"add-generic-password"};
   get_args(entry, &args, true);
   args.emplace_back("-U");
@@ -176,7 +179,7 @@ std::vector<Security_invoker::Entry> Security_invoker::list() {
     }
 
     if (should_add) {
-      result.emplace_back(entry);
+      result.emplace_back(std::move(entry));
     }
   }
 
@@ -200,9 +203,10 @@ std::string Security_invoker::invoke(const std::vector<std::string> &args,
 
   if (!input.empty()) {
     app.write(input.c_str(), input.length());
-    app.finish_writing();
   }
 
+  app.finish_writing();
+
   std::string output = shcore::str_strip(app.read_all());
   const auto exit_code = app.wait();
 
diff --git a/mysql-secret-store/login-path/login_path_helper.cc b/mysql-secret-store/login-path/login_path_helper.cc
index 7facacfd70..e6a3283202 100644
--- a/mysql-secret-store/login-path/login_path_helper.cc
+++ b/mysql-secret-store/login-path/login_path_helper.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -29,8 +29,13 @@
 #include <my_default.h>
 #include <mysql.h>
 
+#include <cassert>
+#include <iterator>
 #include <memory>
+#include <string>
+#include <string_view>
 #include <utility>
+#include <vector>
 
 #include "mysql-secret-store/login-path/entry.h"
 #include "mysqlshdk/libs/db/connection_options.h"
@@ -54,42 +59,50 @@ namespace login_path {
 
 namespace {
 
-constexpr auto k_secret_type_password = "password";
+constexpr std::string_view k_secret_type_password = "password";
+constexpr std::string_view k_scheme_delimiter = ":/";
+constexpr std::string_view k_mysqlsh_scheme = "mysqlsh+";
+
+/**
+ * Parses the INI file outputted by mysql_config_editor.
+ */
 std::vector<Entry> parse_ini(const std::string &ini) {
+  if (ini.empty()) {
+    return {};
+  }
+
   // mysql_config_editor has very structured output, so parsing is simplified
   std::vector<Entry> result;
 
-  if (!ini.empty()) {
-    for (const auto &line : shcore::str_split(ini, "\r\n", -1, true)) {
-      if (line[0] == '[') {
-        result.emplace_back();
-        auto name = line.substr(1, line.length() - 2);
-        auto scheme = name.substr(0, 6);
-        if (scheme == "ssh://") {
-          result.back().scheme = k_scheme_name_ssh;
-        } else if (scheme == "file:/") {
-          result.back().scheme = k_scheme_name_file;
-        }
-        result.back().name = std::move(name);
+  for (const auto &line : shcore::str_split(ini, "\r\n", -1, true)) {
+    if (line[0] == '[') {
+      result.emplace_back();
+      auto name = line.substr(1, line.length() - 2);
+
+      if (const auto pos = name.find(k_scheme_delimiter);
+          std::string_view::npos != pos) {
+        result.back().scheme = name.substr(0, pos);
+      }
+
+      result.back().name = std::move(name);
+    } else {
+      const auto pos = line.find(" = ");
+      const auto option = line.substr(0, pos);
+      auto value = shcore::unquote_string(line.substr(pos + 3), '"');
+
+      if (option == "user") {
+        result.back().user = std::move(value);
+      } else if (option == "host") {
+        result.back().host = std::move(value);
+      } else if (option == "socket") {
+        result.back().socket = std::move(value);
+      } else if (option == "port") {
+        result.back().port = std::move(value);
+      } else if (option == "password") {
+        result.back().has_password = true;
       } else {
-        const auto pos = line.find(" = ");
-        const auto option = line.substr(0, pos);
-        const auto value = shcore::unquote_string(line.substr(pos + 3), '"');
-
-        if (option == "user") {
-          result.back().user = value;
-        } else if (option == "host") {
-          result.back().host = value;
-        } else if (option == "socket") {
-          result.back().socket = value;
-        } else if (option == "port") {
-          result.back().port = value;
-        } else if (option == "password") {
-          result.back().has_password = true;
-        } else {
-          throw Helper_exception{
-              "Failed to parse INI output, unknown option: " + option};
-        }
+        throw Helper_exception{"Failed to parse INI output, unknown option: " +
+                               option};
       }
     }
   }
@@ -97,105 +110,179 @@ std::vector<Entry> parse_ini(const std::string &ini) {
   return result;
 }
 
-void validate_secret_type(const common::Secret_id &id) {
-  if (id.secret_type != k_secret_type_password) {
-    throw Helper_exception{
-        "The login-path helper is only able to store passwords."};
+/**
+ * Cleans the name, so that in can be stored in an INI file.
+ */
+std::string clean_name(const std::string &name) {
+  // name is used as a section name in an INI file, cannot contain [] characters
+  return shcore::str_replace(name, "[", "%5B", "]", "%5D");
+}
+
+/**
+ * Normalizes a URL which is not used to connect to a MySQL server (has a file
+ * or an ssh scheme).
+ */
+std::string normalize_non_mysql_url(const std::string &scheme,
+                                    const std::string &url) {
+  assert(!scheme.empty());
+
+  std::unique_ptr<mysqlshdk::db::uri::IUri_parsable> parsable;
+
+  if (k_scheme_name_file == scheme) {
+    parsable = std::make_unique<mysqlshdk::db::uri::File_uri>(url);
+  } else if (k_scheme_name_ssh == scheme) {
+    parsable = std::make_unique<mysqlshdk::ssh::Ssh_connection_options>(url);
+  } else {
+    throw Helper_exception{"Unsupported scheme: " + scheme};
   }
+
+  assert(parsable);
+
+  return parsable->as_uri(mysqlshdk::db::uri::formats::scheme_user_transport());
 }
 
-std::string get_url(const Entry &entry, bool encode) {
-  if (encode) {
-    mysqlshdk::db::uri::Generic_uri uri_opts;
-    mysqlshdk::db::uri::File_uri file_opts;
-    mysqlshdk::db::Connection_options conn_opts;
-    mysqlshdk::ssh::Ssh_connection_options ssh_opts;
-
-    mysqlshdk::db::uri::IUri_parsable *options;
-
-    if (!entry.scheme.empty()) {
-      if (conn_opts.is_allowed_scheme(entry.scheme))
-        options = &conn_opts;
-      else if (ssh_opts.is_allowed_scheme(entry.scheme))
-        options = &ssh_opts;
-      else if (file_opts.is_allowed_scheme(entry.scheme))
-        options = &file_opts;
-      else
-        options = &uri_opts;
-    } else {
-      if (!entry.socket.empty())
-        options = &conn_opts;
-      else
-        options = &uri_opts;
-    }
+/**
+ * Normalizes a URL which is used to connect to a MySQL server (doesn't have a
+ * scheme).
+ */
+std::string normalize_mysql_url(const Entry &entry) {
+  assert(entry.scheme.empty());
+
+  // URLs which are not encoded are used as an internal detail and help to
+  // identify entries which were not created by us. Such entries can have both
+  // socket and port set, which means Connection_options cannot be used.
+  std::string normalized;
+
+  normalized.reserve(64);
+
+  if (!entry.user.empty()) {
+    normalized += entry.user;
+    normalized += '@';
+  }
+
+  if (entry.socket.empty()) {
+    normalized += entry.host;
 
-    if (!entry.user.empty()) {
-      options->set(mysqlshdk::db::kUser, entry.user);
-    }
-    if (!entry.host.empty()) {
-      options->set(mysqlshdk::db::kHost, entry.host);
-    }
     if (!entry.port.empty()) {
-      options->set(mysqlshdk::db::kPort, std::stoi(entry.port));
+      normalized += ':';
+      normalized += entry.port;
     }
-    if (!entry.socket.empty()) {
-      options->set(mysqlshdk::db::kSocket, entry.socket);
+  } else {
+    normalized += entry.socket;
+  }
+
+  return normalized;
+}
+
+/**
+ * Initializes entry read from the INI file.
+ */
+void initialize_entry(Entry *entry) {
+  entry->secret_type = k_secret_type_password;
+
+  if (entry->scheme.empty()) {
+    // entry was created by helper if its name matches the name that we would
+    // write
+    entry->entry_type = clean_name(normalize_mysql_url(*entry)) == entry->name
+                            ? Entry_type::SET_BY_HELPER
+                            : Entry_type::EXTERNAL;
+  } else {
+    if (k_scheme_name_file == entry->scheme ||
+        k_scheme_name_ssh == entry->scheme) {
+      // we assume that all entries with file/ssh scheme are created by helper
+      entry->entry_type = Entry_type::SET_BY_HELPER;
+    } else if (entry->scheme.starts_with(k_mysqlsh_scheme)) {
+      entry->entry_type = Entry_type::SET_BY_HELPER;
+      entry->secret_type = entry->scheme.substr(k_mysqlsh_scheme.length());
+    } else {
+      entry->entry_type = Entry_type::EXTERNAL;
     }
+  }
+}
 
-    try {
-      if (entry.scheme == k_scheme_name_file) {
-        return mysqlshdk::db::uri::File_uri(entry.name)
-            .as_uri(mysqlshdk::db::uri::formats::scheme_user_transport());
+/**
+ * ID of an entry with 'password' secret type.
+ */
+std::string get_url_as_id(const Entry &entry) {
+  assert(k_secret_type_password == entry.secret_type);
 
-      } else if (entry.scheme == k_scheme_name_ssh) {
-        return mysqlshdk::ssh::Ssh_connection_options(entry.name)
-            .as_uri(mysqlshdk::db::uri::formats::scheme_user_transport());
-      }
+  if (!entry.scheme.empty()) {
+    try {
+      return normalize_non_mysql_url(entry.scheme, entry.name);
     } catch (...) {
       // no op we do nothing here, just fallback to the default handling as it
       // looks like name is something we can't handle
     }
+  }
 
-    return options->as_uri(mysqlshdk::db::uri::formats::user_transport());
-
+  mysqlshdk::db::uri::Generic_uri uri_opts;
+  mysqlshdk::db::uri::File_uri file_opts;
+  mysqlshdk::db::Connection_options conn_opts;
+  mysqlshdk::ssh::Ssh_connection_options ssh_opts;
+
+  mysqlshdk::db::uri::IUri_parsable *options;
+
+  if (!entry.scheme.empty()) {
+    if (conn_opts.is_allowed_scheme(entry.scheme))
+      options = &conn_opts;
+    else if (ssh_opts.is_allowed_scheme(entry.scheme))
+      options = &ssh_opts;
+    else if (file_opts.is_allowed_scheme(entry.scheme))
+      options = &file_opts;
+    else
+      options = &uri_opts;
   } else {
-    // for those two, we don't change the name
-    if (entry.scheme == k_scheme_name_file ||
-        entry.scheme == k_scheme_name_ssh) {
-      return entry.name;
-    }
-    // URLs which are not encoded are used as an internal detail and help
-    // to identify entries which were not created by us. Such entries can have
-    // both socket and port set, which means Connection_options cannot be used.
-    return (entry.user.empty() ? "" : entry.user + "@") +
-           (entry.socket.empty()
-                ? entry.host + (entry.port.empty() ? "" : ":" + entry.port)
-                : entry.socket);
+    if (!entry.socket.empty())
+      options = &conn_opts;
+    else
+      options = &uri_opts;
   }
-}
 
-std::string get_encoded_url(const Entry &entry) { return get_url(entry, true); }
+  if (!entry.user.empty()) {
+    options->set(mysqlshdk::db::kUser, entry.user);
+  }
+  if (!entry.host.empty()) {
+    options->set(mysqlshdk::db::kHost, entry.host);
+  }
+  if (!entry.port.empty()) {
+    options->set(mysqlshdk::db::kPort, std::stoi(entry.port));
+  }
+  if (!entry.socket.empty()) {
+    options->set(mysqlshdk::db::kSocket, entry.socket);
+  }
 
-std::string clean_name(const std::string &name) {
-  // name is used as a section name in an INI file, cannot contain [] characters
-  return shcore::str_replace(shcore::str_replace(name, "[", "%5B"), "]", "%5D");
+  return options->as_uri(
+      entry.scheme.empty()
+          ? mysqlshdk::db::uri::formats::user_transport()
+          : mysqlshdk::db::uri::formats::scheme_user_transport());
 }
 
-std::string get_name(const Entry &entry) {
-  return clean_name(get_url(entry, false));
-}
+/**
+ * Gets the ID of the given entry.
+ */
+std::string get_id(const Entry &entry) {
+  assert(!entry.secret_type.empty());
 
-std::string get_name(mysqlshdk::db::uri::Uri_serializable *connection,
-                     const Entry &entry) {
-  assert(connection != nullptr);
-  std::string name;
-  if (entry.scheme == k_scheme_name_file || entry.scheme == k_scheme_name_ssh) {
-    name = connection->as_uri(
-        mysqlshdk::db::uri::formats::scheme_user_transport());
+  if (k_secret_type_password == entry.secret_type) {
+    return get_url_as_id(entry);
   } else {
-    name = get_url(entry, false);
+    // we're guaranteed to find the delimiter, because this is a non-password
+    // entry, whose type is extracted from a scheme
+    const auto pos = entry.name.find(k_scheme_delimiter);
+    assert(std::string::npos != pos);
+    return shcore::pctdecode(
+        std::string_view{entry.name}.substr(pos + k_scheme_delimiter.length()));
   }
-  return clean_name(name);
+}
+
+/**
+ * Checks if secret contains only valid characters.
+ */
+bool validate_secret(const std::string &secret) {
+  return std::string::npos ==
+         secret.find_first_of(
+             "\x00\x03\x04\x0A\x0D\x0F\x11\x12\x13\x15\x16\x17\x19\x1A\x1C\x7F",
+             0, 16);
 }
 
 }  // namespace
@@ -228,6 +315,10 @@ void Login_path_helper::store(const common::Secret &secret) {
         "prepended with '\\', decreasing available space."};
   }
 
+  if (!validate_secret(secret.secret)) {
+    throw get_helper_exception(Helper_exception_code::INVALID_SECRET);
+  }
+
   m_invoker.store(to_entry(secret.id), s);
 }
 
@@ -255,7 +346,7 @@ void Login_path_helper::erase(const common::Secret_id &id) {
     } else {
       // two entries mean that original login-path section had both port and
       // socket, we need to remove only port or only socket
-      mysqlshdk::db::uri::Generic_uri options{id.url};
+      mysqlshdk::db::uri::Generic_uri options{id.id};
 
       if (options.has_value(mysqlshdk::db::kPort)) {
         m_invoker.erase_port(entry);
@@ -268,10 +359,14 @@ void Login_path_helper::erase(const common::Secret_id &id) {
   }
 }
 
-void Login_path_helper::list(std::vector<common::Secret_id> *secrets) {
-  for (const auto &entry : list()) {
-    secrets->emplace_back(
-        common::Secret_id{entry.secret_type, get_encoded_url(entry)});
+void Login_path_helper::list(std::vector<common::Secret_id> *secrets,
+                             std::optional<std::string> secret_type) {
+  for (auto &entry : list()) {
+    if (!secret_type.has_value() || entry.secret_type == *secret_type) {
+      auto id = get_id(entry);
+      secrets->emplace_back(
+          common::Secret_id{std::move(entry.secret_type), std::move(id)});
+    }
   }
 }
 
@@ -283,16 +378,13 @@ std::vector<Entry> Login_path_helper::list() {
   for (auto &entry : intermediate_result) {
     // we're not interested in entries without passwords (i.e. client section)
     if (entry.has_password) {
-      entry.secret_type = k_secret_type_password;
+      initialize_entry(&entry);
 
-      if (entry.name == get_name(entry)) {
+      if (Entry_type::SET_BY_HELPER == entry.entry_type) {
         // entry was created by helper
-        entry.entry_type = Entry_type::SET_BY_HELPER;
-
         result.emplace_back(std::move(entry));
       } else {
         // unknown entry, validate if it's suitable
-        entry.entry_type = Entry_type::EXTERNAL;
 
         if (!entry.socket.empty() && !entry.host.empty()) {
           if (!mysqlshdk::utils::Net::is_local_address(entry.host)) {
@@ -333,18 +425,17 @@ std::vector<Entry> Login_path_helper::list() {
 }
 
 Entry Login_path_helper::find(const common::Secret_id &secret) {
-  validate_secret_type(secret);
-
   auto entries = list();
   std::vector<Entry> result;
   const auto secret_entry = to_entry(secret);
-  const auto encoded_entry = get_encoded_url(secret_entry);
+  const auto id = get_id(secret_entry);
 
-  std::copy_if(entries.begin(), entries.end(), std::back_inserter(result),
-               [&secret_entry, &encoded_entry](const Entry &entry) {
-                 return get_encoded_url(entry) == encoded_entry &&
-                        entry.secret_type == secret_entry.secret_type;
-               });
+  std::copy_if(
+      std::make_move_iterator(entries.begin()),
+      std::make_move_iterator(entries.end()), std::back_inserter(result),
+      [secret_type = secret_entry.secret_type, &id](const Entry &entry) {
+        return get_id(entry) == id && entry.secret_type == secret_type;
+      });
 
   switch (result.size()) {
     case 0:
@@ -364,46 +455,60 @@ Entry Login_path_helper::find(const common::Secret_id &secret) {
 
     default: {
       std::vector<std::string> data;
+
       for (const auto &entry : result) {
-        data.push_back(get_encoded_url(entry));
+        data.push_back(get_id(entry));
       }
 
       throw Helper_exception{shcore::str_format(
           "Multiple entries match the secret, unable to deduce which one to "
           "return: [%s], [%s], [%s]",
-          secret.url.c_str(), encoded_entry.c_str(),
-          shcore::str_join(data, ", ").c_str())};
+          secret.id.c_str(), id.c_str(), shcore::str_join(data, ", ").c_str())};
     }
   }
 }
 
 Entry Login_path_helper::to_entry(const common::Secret_id &secret) const {
-  validate_secret_type(secret);
-
   Entry entry;
   entry.secret_type = secret.secret_type;
   entry.has_password = true;
   entry.entry_type = Entry_type::SET_BY_HELPER;
 
-  mysqlshdk::db::uri::Generic_uri options{secret.url};
+  if (k_secret_type_password == entry.secret_type) {
+    mysqlshdk::db::uri::Generic_uri options{secret.id};
 
-  if (options.has_value(mysqlshdk::db::kScheme)) {
-    entry.scheme = options.get(mysqlshdk::db::kScheme);
-  }
-  if (options.has_value(mysqlshdk::db::kUser)) {
-    entry.user = options.get(mysqlshdk::db::kUser);
-  }
-  if (options.has_value(mysqlshdk::db::kHost)) {
-    entry.host = options.get(mysqlshdk::db::kHost);
-  }
-  if (options.has_value(mysqlshdk::db::kPort)) {
-    entry.port = std::to_string(options.get_numeric(mysqlshdk::db::kPort));
-  }
-  if (options.has_value(mysqlshdk::db::kSocket)) {
-    entry.socket = options.get(mysqlshdk::db::kSocket);
-  }
+    if (options.has_value(mysqlshdk::db::kScheme)) {
+      entry.scheme = options.get(mysqlshdk::db::kScheme);
+    }
+    if (options.has_value(mysqlshdk::db::kUser)) {
+      entry.user = options.get(mysqlshdk::db::kUser);
+    }
+    if (options.has_value(mysqlshdk::db::kHost)) {
+      entry.host = options.get(mysqlshdk::db::kHost);
+    }
+    if (options.has_value(mysqlshdk::db::kPort)) {
+      entry.port = std::to_string(options.get_numeric(mysqlshdk::db::kPort));
+    }
+    if (options.has_value(mysqlshdk::db::kSocket)) {
+      entry.socket = options.get(mysqlshdk::db::kSocket);
+    }
+
+    if (k_scheme_name_file == entry.scheme ||
+        k_scheme_name_ssh == entry.scheme) {
+      entry.name = normalize_non_mysql_url(entry.scheme, secret.id);
+    } else {
+      entry.name = normalize_mysql_url(entry);
+    }
 
-  entry.name = get_name(&options, entry);
+    entry.name = clean_name(entry.name);
+  } else {
+    entry.scheme = k_mysqlsh_scheme;
+    entry.scheme += entry.secret_type;
+
+    entry.name = entry.scheme;
+    entry.name += k_scheme_delimiter;
+    entry.name += shcore::pctencode(secret.id);
+  }
 
   return entry;
 }
@@ -417,7 +522,7 @@ std::string Login_path_helper::load(const Entry &entry) {
   MEM_ROOT argv_alloc{};  // uses: PSI_NOT_INSTRUMENTED, 512
   shcore::Scoped_callback release_argv_alloc(
       [&argv_alloc]() { argv_alloc.Clear(); });
-  std::string argv_name = name();
+  const auto argv_name = name();
   char *argv_name_ptr = const_cast<char *>(argv_name.c_str());
   // my_load_defaults() requires argv[0] is not null
   char **new_argv = &argv_name_ptr;
diff --git a/mysql-secret-store/login-path/login_path_helper.h b/mysql-secret-store/login-path/login_path_helper.h
index 63d4cad6c6..94d639709b 100644
--- a/mysql-secret-store/login-path/login_path_helper.h
+++ b/mysql-secret-store/login-path/login_path_helper.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -50,7 +50,8 @@ class Login_path_helper : public common::Helper {
 
   void erase(const common::Secret_id &) override;
 
-  void list(std::vector<common::Secret_id> *) override;
+  void list(std::vector<common::Secret_id> *,
+            std::optional<std::string>) override;
 
  private:
   std::vector<Entry> list();
diff --git a/mysql-secret-store/secret-service/secret_service_helper.cc b/mysql-secret-store/secret-service/secret_service_helper.cc
index 2c027d7191..3e02f3471e 100644
--- a/mysql-secret-store/secret-service/secret_service_helper.cc
+++ b/mysql-secret-store/secret-service/secret_service_helper.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -25,7 +25,13 @@
 
 #include "mysql-secret-store/secret-service/secret_service_helper.h"
 
+#include <algorithm>
+#include <array>
+#include <chrono>
+#include <string>
+#include <thread>
 #include <utility>
+#include <vector>
 
 #include "mysqlshdk/libs/utils/utils_general.h"
 #include "mysqlshdk/libs/utils/utils_string.h"
@@ -42,15 +48,18 @@ namespace {
 
 #define SECRET_TYPE_KEY "secret_type"
 #define SECRET_URL_KEY "secret_url"
+#define SECRET_ID_KEY "secret_id"
 
 #define MAKE_ATTRIBUTE(key) "attribute." key
 
 constexpr auto k_secret_store_key = "secret_store";
 constexpr auto k_secret_type_key = SECRET_TYPE_KEY;
 constexpr auto k_secret_url_key = SECRET_URL_KEY;
+constexpr auto k_secret_id_key = SECRET_ID_KEY;
 
 constexpr auto k_secret_type_attribute = MAKE_ATTRIBUTE(SECRET_TYPE_KEY);
 constexpr auto k_secret_url_attribute = MAKE_ATTRIBUTE(SECRET_URL_KEY);
+constexpr auto k_secret_id_attribute = MAKE_ATTRIBUTE(SECRET_ID_KEY);
 
 #undef MAKE_ATTRIBUTE
 #undef SECRET_TYPE_KEY
@@ -74,15 +83,16 @@ void parse_list(const std::string &list,
         secrets->emplace_back();
         needs_type = needs_url = true;
       } else {
-        auto option = shcore::str_split(line, "=", -1, true);
+        auto option = shcore::str_split(line, "=", 1, true);
         auto key = shcore::str_strip(option[0]);
         auto value = shcore::str_strip(option[1]);
 
         if (k_secret_type_attribute == key) {
           secrets->back().secret_type = value;
           needs_type = false;
-        } else if (k_secret_url_attribute == key) {
-          secrets->back().url = value;
+        } else if (k_secret_url_attribute == key ||
+                   k_secret_id_attribute == key) {
+          secrets->back().id = value;
           needs_url = false;
         }
       }
@@ -92,6 +102,10 @@ void parse_list(const std::string &list,
   verify_secret_id();
 }
 
+bool validate_string(const std::string &str) {
+  return shcore::is_valid_utf8(str);
+}
+
 }  // namespace
 
 Secret_service_helper::Secret_service_helper()
@@ -106,8 +120,24 @@ void Secret_service_helper::check_requirements() {
 }
 
 void Secret_service_helper::store(const common::Secret &secret) {
-  m_invoker.store(get_label(secret.id), get_attributes(secret.id),
-                  secret.secret);
+  if (!validate_string(secret.id.id)) {
+    throw get_helper_exception(Helper_exception_code::INVALID_SECRET_ID);
+  }
+
+  if (!validate_string(secret.secret)) {
+    throw get_helper_exception(Helper_exception_code::INVALID_SECRET);
+  }
+
+  const auto attributes = get_attributes(secret.id);
+
+  m_invoker.store(get_label(secret.id), attributes, secret.secret);
+
+  // wait till DBUS propagates the message, but don't report any errors, secret
+  // was successfully stored
+  try {
+    get(attributes);
+  } catch (...) {
+  }
 }
 
 void Secret_service_helper::get(const common::Secret_id &id,
@@ -123,8 +153,18 @@ void Secret_service_helper::erase(const common::Secret_id &id) {
   m_invoker.erase(attributes);
 }
 
-void Secret_service_helper::list(std::vector<common::Secret_id> *secrets) {
+void Secret_service_helper::list(std::vector<common::Secret_id> *secrets,
+                                 std::optional<std::string> secret_type) {
   parse_list(list(), secrets);
+
+  if (secret_type.has_value()) {
+    secrets->erase(std::remove_if(secrets->begin(), secrets->end(),
+                                  [secret_type = *secret_type](
+                                      const common::Secret_id &id) {
+                                    return id.secret_type != secret_type;
+                                  }),
+                   secrets->end());
+  }
 }
 
 Secret_tool_invoker::Attributes Secret_service_helper::get_attributes(
@@ -133,25 +173,43 @@ Secret_tool_invoker::Attributes Secret_service_helper::get_attributes(
 
   attributes.emplace(std::make_pair(k_secret_store_key, name()));
   attributes.emplace(std::make_pair(k_secret_type_key, id.secret_type));
-  attributes.emplace(std::make_pair(k_secret_url_key, id.url));
+
+  if ("password" == id.secret_type) {
+    attributes.emplace(std::make_pair(k_secret_url_key, id.id));
+  } else {
+    attributes.emplace(std::make_pair(k_secret_id_key, id.id));
+  }
 
   return attributes;
 }
 
 std::string Secret_service_helper::get_label(const common::Secret_id &id) {
-  return "Secret for " + id.url + " (" + id.secret_type + ")";
+  return "Secret for " + id.id + " (" + id.secret_type + ")";
 }
 
 std::string Secret_service_helper::get(
     const Secret_tool_invoker::Attributes &attributes) {
-  try {
-    return m_invoker.get(attributes);
-  } catch (const Helper_exception &ex) {
-    if (ex.what() ==
-        std::string{"Process \"secret-tool\" finished with exit code 1: "}) {
-      throw get_helper_exception(Helper_exception_code::NO_SUCH_SECRET);
-    } else {
-      throw;
+  static constexpr std::array k_retry_times = {200, 400};
+
+  auto sleep_time = k_retry_times.begin();
+
+  while (true) {
+    try {
+      return m_invoker.get(attributes);
+    } catch (const Helper_exception &ex) {
+      if (k_retry_times.end() != sleep_time) {
+        // retry in case of an error, DBUS sometimes is slow to process messages
+        std::this_thread::sleep_for(std::chrono::milliseconds{*sleep_time});
+        ++sleep_time;
+        continue;
+      }
+
+      if (ex.what() ==
+          std::string{"Process \"secret-tool\" finished with exit code 1: "}) {
+        throw get_helper_exception(Helper_exception_code::NO_SUCH_SECRET);
+      } else {
+        throw;
+      }
     }
   }
 }
@@ -161,7 +219,7 @@ std::string Secret_service_helper::get(const common::Secret_id &id) {
 }
 
 std::string Secret_service_helper::list() {
-  return m_invoker.list({{k_secret_store_key, name()}});
+  return shcore::str_strip(m_invoker.list({{k_secret_store_key, name()}}));
 }
 
 }  // namespace secret_service
diff --git a/mysql-secret-store/secret-service/secret_service_helper.h b/mysql-secret-store/secret-service/secret_service_helper.h
index ede7764f7d..287bd065b0 100644
--- a/mysql-secret-store/secret-service/secret_service_helper.h
+++ b/mysql-secret-store/secret-service/secret_service_helper.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -49,7 +49,8 @@ class Secret_service_helper : public common::Helper {
 
   void erase(const common::Secret_id &) override;
 
-  void list(std::vector<common::Secret_id> *) override;
+  void list(std::vector<common::Secret_id> *,
+            std::optional<std::string>) override;
 
  private:
   Secret_tool_invoker::Attributes get_attributes(const common::Secret_id &id);
diff --git a/mysql-secret-store/secret-service/secret_tool_invoker.cc b/mysql-secret-store/secret-service/secret_tool_invoker.cc
index c21038efe9..53a5f29a9c 100644
--- a/mysql-secret-store/secret-service/secret_tool_invoker.cc
+++ b/mysql-secret-store/secret-service/secret_tool_invoker.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -95,16 +95,18 @@ std::string Secret_tool_invoker::invoke(const std::vector<std::string> &args,
 
   if (has_password) {
     app.write(password.c_str(), password.length());
-    app.finish_writing();
   }
 
-  std::string output = shcore::str_strip(app.read_all());
+  app.finish_writing();
+
+  std::string output = app.read_all();
   const auto exit_code = app.wait();
 
   if (0 != exit_code) {
     throw Helper_exception{"Process \"" + std::string{process_args[0]} +
                            "\" finished with exit code " +
-                           std::to_string(exit_code) + ": " + output};
+                           std::to_string(exit_code) + ": " +
+                           shcore::str_strip(output)};
   }
 
   return output;
diff --git a/mysql-secret-store/windows-credential/windows_credential_helper.cc b/mysql-secret-store/windows-credential/windows_credential_helper.cc
index b9e02694fb..975abbec8b 100644
--- a/mysql-secret-store/windows-credential/windows_credential_helper.cc
+++ b/mysql-secret-store/windows-credential/windows_credential_helper.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -28,6 +28,7 @@
 #include <WinCred.h>
 
 #include <memory>
+#include <string_view>
 #include <utility>
 
 #include "mysqlshdk/libs/utils/utils_file.h"
@@ -72,22 +73,24 @@ void Windows_credential_helper::check_requirements() {
 }
 
 void Windows_credential_helper::store(const common::Secret &secret) {
-  auto url_keyword = get_url_keyword();
-  auto secret_type_keyword = get_secret_type_keyword();
+  const auto id_keyword = "password" == secret.id.secret_type
+                              ? get_url_keyword()
+                              : get_id_keyword();
+  const auto secret_type_keyword = get_secret_type_keyword();
 
   CREDENTIAL_ATTRIBUTE attributes[2];
 
-  attributes[0].Keyword = (LPSTR)url_keyword.c_str();
+  attributes[0].Keyword = (LPSTR)id_keyword.c_str();
   attributes[0].Flags = 0;
-  attributes[0].ValueSize = secret.id.url.length();
-  attributes[0].Value = (LPBYTE)secret.id.url.c_str();
+  attributes[0].ValueSize = secret.id.id.length();
+  attributes[0].Value = (LPBYTE)secret.id.id.c_str();
 
   attributes[1].Keyword = (LPSTR)secret_type_keyword.c_str();
   attributes[1].Flags = 0;
   attributes[1].ValueSize = secret.id.secret_type.length();
   attributes[1].Value = (LPBYTE)secret.id.secret_type.c_str();
 
-  auto name = get_name(secret.id);
+  const auto name = get_name(secret.id);
   CREDENTIAL cred;
 
   cred.Flags = 0;  // no flags
@@ -111,7 +114,7 @@ void Windows_credential_helper::store(const common::Secret &secret) {
 
 void Windows_credential_helper::get(const common::Secret_id &id,
                                     std::string *secret) {
-  auto name = get_name(id);
+  const auto name = get_name(id);
   PCREDENTIAL cred = nullptr;
 
   if (!CredRead(name.c_str(), k_credential_type, 0, &cred)) {
@@ -130,7 +133,7 @@ void Windows_credential_helper::get(const common::Secret_id &id,
 }
 
 void Windows_credential_helper::erase(const common::Secret_id &id) {
-  auto name = get_name(id);
+  const auto name = get_name(id);
 
   if (!CredDelete(name.c_str(), k_credential_type, 0)) {
     if (GetLastError() == ERROR_NOT_FOUND) {
@@ -142,10 +145,11 @@ void Windows_credential_helper::erase(const common::Secret_id &id) {
   }
 }
 
-void Windows_credential_helper::list(std::vector<common::Secret_id> *secrets) {
+void Windows_credential_helper::list(std::vector<common::Secret_id> *secrets,
+                                     std::optional<std::string> type) {
   PCREDENTIAL *credentials = nullptr;
   DWORD count = 0;
-  auto filter = get_search_pattern();
+  const auto filter = get_search_pattern();
 
   if (!CredEnumerate(filter.c_str(), 0, &count, &credentials)) {
     if (GetLastError() == ERROR_NOT_FOUND) {
@@ -159,30 +163,35 @@ void Windows_credential_helper::list(std::vector<common::Secret_id> *secrets) {
     const std::unique_ptr<PCREDENTIAL, decltype(&CredFree)> deleter{credentials,
                                                                     CredFree};
     const auto url_keyword = get_url_keyword();
+    const auto id_keyword = get_id_keyword();
     const auto secret_type_keyword = get_secret_type_keyword();
 
     for (DWORD i = 0; i < count; ++i) {
-      std::string url;
+      std::string id;
       std::string secret_type;
 
       for (DWORD j = 0; j < credentials[i]->AttributeCount; ++j) {
         const auto &attribute = credentials[i]->Attributes[j];
-        const auto keyword = std::string{attribute.Keyword};
-        auto value = std::string(reinterpret_cast<char *>(attribute.Value),
-                                 attribute.ValueSize);
+        const auto keyword = std::string_view{attribute.Keyword};
+        auto value =
+            std::string(reinterpret_cast<const char *>(attribute.Value),
+                        attribute.ValueSize);
 
-        if (keyword == url_keyword) {
-          url = std::move(value);
+        if (keyword == url_keyword || keyword == id_keyword) {
+          id = std::move(value);
         } else if (keyword == secret_type_keyword) {
           secret_type = std::move(value);
         }
       }
 
-      if (url.empty() || secret_type.empty()) {
+      if (id.empty() || secret_type.empty()) {
         throw Helper_exception{"Invalid entry detected"};
       }
 
-      secrets->emplace_back(common::Secret_id{secret_type, url});
+      if (!type.has_value() || *type == secret_type) {
+        secrets->emplace_back(
+            common::Secret_id{std::move(secret_type), std::move(id)});
+      }
     }
   }
 }
@@ -195,7 +204,7 @@ std::string Windows_credential_helper::get_name_prefix() const {
 std::string Windows_credential_helper::get_name(
     const common::Secret_id &id) const {
   return shcore::str_join(
-      std::vector<std::string>{get_name_prefix(), id.secret_type, id.url},
+      std::vector<std::string>{get_name_prefix(), id.secret_type, id.id},
       k_name_separator);
 }
 
@@ -213,6 +222,10 @@ std::string Windows_credential_helper::get_url_keyword() const {
   return get_keyword_prefix() + "url";
 }
 
+std::string Windows_credential_helper::get_id_keyword() const {
+  return get_keyword_prefix() + "id";
+}
+
 std::string Windows_credential_helper::get_secret_type_keyword() const {
   return get_keyword_prefix() + "secret-type";
 }
diff --git a/mysql-secret-store/windows-credential/windows_credential_helper.h b/mysql-secret-store/windows-credential/windows_credential_helper.h
index b432808165..dc9e0b6c2e 100644
--- a/mysql-secret-store/windows-credential/windows_credential_helper.h
+++ b/mysql-secret-store/windows-credential/windows_credential_helper.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -49,7 +49,8 @@ class Windows_credential_helper : public common::Helper {
 
   void erase(const common::Secret_id &) override;
 
-  void list(std::vector<common::Secret_id> *) override;
+  void list(std::vector<common::Secret_id> *,
+            std::optional<std::string>) override;
 
  private:
   std::string get_name_prefix() const;
@@ -62,6 +63,8 @@ class Windows_credential_helper : public common::Helper {
 
   std::string get_url_keyword() const;
 
+  std::string get_id_keyword() const;
+
   std::string get_secret_type_keyword() const;
 
   DWORD m_persist_mode;
diff --git a/mysqlshdk/include/scripting/types.h b/mysqlshdk/include/scripting/types.h
index fe588911df..9f3fbd195e 100644
--- a/mysqlshdk/include/scripting/types.h
+++ b/mysqlshdk/include/scripting/types.h
@@ -29,6 +29,7 @@
 #include "types_common.h"
 
 #include <algorithm>
+#include <cstdint>
 #include <iterator>
 #include <list>
 #include <map>
@@ -575,8 +576,8 @@ class SHCORE_PUBLIC Argument_list final {
   bool operator==(const Argument_list &other) const;
 
   template <class T>
-  void emplace_back(const T &value) {
-    _args.emplace_back(Value(value));
+  void emplace_back(T &&value) {
+    _args.emplace_back(std::forward<T>(value));
   }
 
  private:
diff --git a/mysqlshdk/include/shellcore/base_session.h b/mysqlshdk/include/shellcore/base_session.h
index cf38dc1903..95aea9e17c 100644
--- a/mysqlshdk/include/shellcore/base_session.h
+++ b/mysqlshdk/include/shellcore/base_session.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2015, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -85,6 +85,8 @@ class Query_attribute_store {
 
 class SHCORE_PUBLIC ShellBaseSession : public shcore::Cpp_object_bridge {
  public:
+  using Client_data = shcore::Dictionary_t;
+
   static std::shared_ptr<ShellBaseSession> wrap_session(
       std::shared_ptr<mysqlshdk::db::ISession> session);
 
@@ -149,11 +151,6 @@ class SHCORE_PUBLIC ShellBaseSession : public shcore::Cpp_object_bridge {
 
   virtual std::shared_ptr<mysqlshdk::db::ISession> get_core_session() const = 0;
 
-  void enable_sql_mode_tracking();
-  bool is_sql_mode_tracking_enabled() const {
-    return m_is_sql_mode_tracking_enabled;
-  }
-
   virtual std::vector<mysqlshdk::db::Query_attribute> query_attributes() const {
     return {};
   }
@@ -166,22 +163,32 @@ class SHCORE_PUBLIC ShellBaseSession : public shcore::Cpp_object_bridge {
 
   std::function<void(const std::string &, bool exists)> update_schema_cache;
 
+  void set_client_data(const std::string &key, const shcore::Value &value);
+  shcore::Value get_client_data(const std::string &key) const;
+
+  virtual std::string track_system_variable(const std::string &variable);
+  bool is_sql_mode_tracking_enabled() const { return m_tracking_sql_mode; }
+
  protected:
   std::string get_quoted_name(std::string_view name);
   // TODO(rennox): Note that these are now stored on the low level session
   // object too, they should be removed from here
   mysqlshdk::db::Connection_options _connection_options;
 
+  std::string get_sql_mode();
+
  protected:
-  std::string sub_query_placeholders(const std::string &query,
+  std::string sub_query_placeholders(std::string_view query,
                                      const shcore::Array_t &args);
 
   int _tx_deep;
 
   Query_attribute_store m_query_attributes;
 
+  Client_data m_client_data;
+  bool m_tracking_sql_mode{false};
+
  private:
-  bool m_is_sql_mode_tracking_enabled = false;
   std::string m_active_custom_sql;
 
   virtual std::shared_ptr<ShellBaseSession> get_shared_this() = 0;
diff --git a/mysqlshdk/include/shellcore/shell_options.h b/mysqlshdk/include/shellcore/shell_options.h
index 0e9ffa027e..b5d65e8b47 100644
--- a/mysqlshdk/include/shellcore/shell_options.h
+++ b/mysqlshdk/include/shellcore/shell_options.h
@@ -40,7 +40,6 @@
 #define SHCORE_SANDBOX_DIR "sandboxDir"
 #define SHCORE_DBA_GTID_WAIT_TIMEOUT "dba.gtidWaitTimeout"
 #define SHCORE_DBA_RESTART_WAIT_TIMEOUT "dba.restartWaitTimeout"
-#define SHCORE_DBA_LOG_SQL "dba.logSql"
 #define SHCORE_DBA_CONNECTIVITY_CHECKS "dba.connectivityChecks"
 #define SHCORE_DBA_VERSION_COMPATIBILITY_CHECKS "dba.versionCompatibilityChecks"
 #define SHCORE_LOG_FILE_NAME "logFile"
@@ -142,11 +141,9 @@ class Shell_options final : public shcore::Options {
     bool db_name_cache = true;
     bool db_name_cache_set = false;
     std::string execute_statement;
-    std::string execute_dba_statement;
     std::string sandbox_directory;
     int dba_gtid_wait_timeout = 60;
     int dba_restart_wait_timeout = 60;
-    int dba_log_sql = 0;
     bool dba_connectivity_checks = false;
     bool dba_version_compatibility_checks = true;
     std::string log_sql;  //< Global SQL logging level
diff --git a/mysqlshdk/libs/aws/aws_config_file.cc b/mysqlshdk/libs/aws/aws_config_file.cc
index 17d30a8723..684785a8b9 100644
--- a/mysqlshdk/libs/aws/aws_config_file.cc
+++ b/mysqlshdk/libs/aws/aws_config_file.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -27,6 +27,7 @@
 
 #include <fstream>
 #include <stdexcept>
+#include <string_view>
 #include <utility>
 
 #include "mysqlshdk/libs/utils/utils_string.h"
@@ -36,13 +37,15 @@ namespace aws {
 
 namespace {
 
-const std::string k_profile_prefix = "profile ";
+constexpr std::string_view k_profile_prefix = "profile ";
+constexpr std::string_view k_default_profile = "default";
 
 }  // namespace
 
 Aws_config_file::Aws_config_file(const std::string &path) : m_path(path) {}
 
-std::optional<Profiles> Aws_config_file::load() const {
+std::optional<Profiles> Aws_config_file::load(
+    bool require_profile_prefix) const {
   const auto &path =
 #ifdef _WIN32
       shcore::utf8_to_wide
@@ -56,6 +59,9 @@ std::optional<Profiles> Aws_config_file::load() const {
 
   Profiles profiles;
   Profile *current_profile = nullptr;
+  // profile to hold settings of sections which are ignored (not prefixed with
+  // k_profile_prefix when require_profile_prefix is true)
+  Profile ignored_section{"ignored-section"};
 
   // lines which are not a profile name, a key-value pair or a comment result in
   // a failure
@@ -86,16 +92,25 @@ std::optional<Profiles> Aws_config_file::load() const {
       const auto end = line.find(']');
 
       if (end != std::string::npos) {
-        auto profile = shcore::str_strip(line.substr(1, end - 1));
-
-        // Profiles in the config file have a "profile " prefix (except for the
-        // "default" profile). Profiles in the credentials file do not have a
-        // prefix, but we do not enforce this (similarly to the AWS SDK).
-        if (shcore::str_beginswith(profile, k_profile_prefix)) {
-          profile = profile.substr(k_profile_prefix.length());
+        auto section = shcore::str_strip(line.substr(1, end - 1));
+
+        if (!require_profile_prefix || k_default_profile == section ||
+            shcore::str_beginswith(section, k_profile_prefix)) {
+          // if we require a profile prefix, then section name is either
+          // 'default' or is prefixed with 'profile '; given that the former is
+          // shorter than the latter, if section name is longer than 'default',
+          // then it's prefixed, and prefix has to be removed
+          static_assert(k_profile_prefix.length() > k_default_profile.length());
+
+          if (require_profile_prefix &&
+              section.length() > k_default_profile.length()) {
+            section = section.substr(k_profile_prefix.length());
+          }
+
+          current_profile = profiles.get_or_create(section);
+        } else {
+          current_profile = &ignored_section;
         }
-
-        current_profile = profiles.get_or_create(profile);
       } else {
         fail("missing closing square bracket");
       }
diff --git a/mysqlshdk/libs/aws/aws_config_file.h b/mysqlshdk/libs/aws/aws_config_file.h
index 0320a7c163..c218c898a0 100644
--- a/mysqlshdk/libs/aws/aws_config_file.h
+++ b/mysqlshdk/libs/aws/aws_config_file.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -59,11 +59,14 @@ class Aws_config_file final {
   /**
    * Loads the configuration from the file.
    *
+   * @param require_profile_prefix If true, only 'default' section and sections
+   * prefixed with 'profile ' are returned.
+   *
    * @returns Loaded profiles, if the requested file exists
    *
    * @throws std::runtime_error if file is malformed
    */
-  std::optional<Profiles> load() const;
+  std::optional<Profiles> load(bool require_profile_prefix) const;
 
  private:
   std::string m_path;
diff --git a/mysqlshdk/libs/aws/aws_credentials_provider.cc b/mysqlshdk/libs/aws/aws_credentials_provider.cc
index 451f8b58db..ad41c3a465 100644
--- a/mysqlshdk/libs/aws/aws_credentials_provider.cc
+++ b/mysqlshdk/libs/aws/aws_credentials_provider.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -41,7 +41,7 @@ Aws_credentials_provider::Aws_credentials_provider(Context context)
 Aws_credentials_provider::Credentials_ptr_t
 Aws_credentials_provider_traits::convert(
     const Aws_credentials_provider &self,
-    const Intermediate_credentials &credentials) {
+    Intermediate_credentials &&credentials) {
   if (credentials.access_key_id.has_value() !=
       credentials.secret_access_key.has_value()) {
     throw std::runtime_error("Partial AWS credentials found in " + self.name() +
@@ -70,8 +70,10 @@ Aws_credentials_provider_traits::convert(
     }
 
     return std::make_shared<Aws_credentials>(
-        *credentials.access_key_id, *credentials.secret_access_key,
-        credentials.session_token.value_or(std::string{}), expiration);
+        *std::move(credentials.access_key_id),
+        *std::move(credentials.secret_access_key),
+        std::move(credentials.session_token).value_or(std::string{}),
+        std::move(expiration));
   }
 
   return {};
diff --git a/mysqlshdk/libs/aws/aws_credentials_provider.h b/mysqlshdk/libs/aws/aws_credentials_provider.h
index 032cd593b4..7b20c64bcc 100644
--- a/mysqlshdk/libs/aws/aws_credentials_provider.h
+++ b/mysqlshdk/libs/aws/aws_credentials_provider.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -53,7 +53,7 @@ struct Aws_credentials_provider_traits {
   };
 
   static std::shared_ptr<Credentials_t> convert(
-      const Provider_t &self, const Intermediate_credentials &credentials);
+      const Provider_t &self, Intermediate_credentials &&credentials);
 };
 
 class Aws_credentials_provider
diff --git a/mysqlshdk/libs/aws/aws_settings.cc b/mysqlshdk/libs/aws/aws_settings.cc
index c1c3349fc6..49949cd8ed 100644
--- a/mysqlshdk/libs/aws/aws_settings.cc
+++ b/mysqlshdk/libs/aws/aws_settings.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2024, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -109,7 +109,7 @@ void Settings::load_profiles() {
     const auto context = is_config_file ? "config" : "credentials";
     const auto &path = get_string(s);
 
-    auto loaded_profiles = Aws_config_file{path}.load();
+    auto loaded_profiles = Aws_config_file{path}.load(is_config_file);
 
     if (!loaded_profiles.has_value()) {
       log_warning("Could not open the %s file at '%s': %s.", context,
diff --git a/mysqlshdk/libs/db/connection_options.cc b/mysqlshdk/libs/db/connection_options.cc
index fa063c62b9..8c3b798773 100644
--- a/mysqlshdk/libs/db/connection_options.cc
+++ b/mysqlshdk/libs/db/connection_options.cc
@@ -309,37 +309,35 @@ void Connection_options::clear_needs_password(int factor) {
 }
 
 void Connection_options::set_ssh_options(
-    const mysqlshdk::ssh::Ssh_connection_options &options) {
-  m_ssh_options = options;
-}
+    mysqlshdk::ssh::Ssh_connection_options &&options) {
+  m_ssh_options = std::move(options);
 
-ssh::Ssh_connection_options &Connection_options::get_ssh_options_handle(
-    int fallback_port) {
-  if (m_ssh_options.has_data()) {
-    int mysql_port = fallback_port;
-    if (m_port.has_value()) {
-      mysql_port = *m_port;
-    } else if (has_scheme()) {
-      if (get_scheme() == "mysql") {
-        mysql_port = k_default_mysql_port;
-      } else if (get_scheme() == "mysqlx") {
-        mysql_port = k_default_mysql_x_port;
-      }
-    }
+  if (!m_ssh_options.has_data()) {
+    return;
+  }
 
-    if (has_host() && mysql_port != 0) {
-      m_ssh_options.set_remote_host(get_host());
+  // set the remote host & port if already available
 
-      m_ssh_options.clear_remote_port();
-      m_ssh_options.set_remote_port(mysql_port);
-    } else {
-      throw std::invalid_argument(
-          "Host and port for database are required in advance when using "
-          "SSH tunneling functionality");
+  if (has_host()) {
+    m_ssh_options.set_remote_host(get_host());
+  }
+
+  int mysql_port = 0;
+
+  if (m_port.has_value()) {
+    mysql_port = *m_port;
+  } else if (has_scheme()) {
+    if (get_scheme() == "mysql") {
+      mysql_port = k_default_mysql_port;
+    } else if (get_scheme() == "mysqlx") {
+      mysql_port = k_default_mysql_x_port;
     }
   }
 
-  return m_ssh_options;
+  if (mysql_port) {
+    m_ssh_options.clear_remote_port();
+    m_ssh_options.set_remote_port(mysql_port);
+  }
 }
 
 bool Connection_options::has_data() const {
@@ -425,11 +423,21 @@ void Connection_options::set_host(const std::string &host) {
     m_transport_type = Transport_type::Tcp;
   }
 #endif  // _WIN32
+
+  if (m_ssh_options.has_data()) {
+    m_ssh_options.clear_remote_host();
+    m_ssh_options.set_remote_host(host);
+  }
 }
 
 void Connection_options::set_port(int port) {
   m_port = port;
   m_default_transport_type = Transport_type::Tcp;
+
+  if (m_ssh_options.has_data()) {
+    m_ssh_options.clear_remote_port();
+    m_ssh_options.set_remote_port(port);
+  }
 }
 
 bool Connection_options::is_extra_option(const std::string &option) {
@@ -484,7 +492,8 @@ void Connection_options::set(const std::string &name,
   }
 #endif
   else if (is_extra_option(name)) {
-    if (name == kGetServerPublicKey || name == kClientInteractive) {
+    if (name == kGetServerPublicKey || name == kClientInteractive ||
+        name == kLocalInfile) {
       if (!is_bool_value(value)) {
         throw std::invalid_argument(
             shcore::str_format("Invalid value '%s' for '%s'. Allowed "
@@ -502,7 +511,7 @@ void Connection_options::set(const std::string &name,
       } catch (...) {
         throw std::invalid_argument(
             "The value of '"s + kWebauthnClientDeviceIndex +
-            "' must be an integer equal or larger than 0."s);
+            "' must be an integer equal to or greater than 0."s);
       }
     }
     m_extra_options.set(name, value, Set_mode::CREATE);
diff --git a/mysqlshdk/libs/db/connection_options.h b/mysqlshdk/libs/db/connection_options.h
index 02fcb760f7..cfe66a992c 100644
--- a/mysqlshdk/libs/db/connection_options.h
+++ b/mysqlshdk/libs/db/connection_options.h
@@ -85,7 +85,7 @@ class SHCORE_PUBLIC Connection_options : public IConnection {
 
   void set_login_options_from(const Connection_options &options);
   void set_ssl_options(const Ssl_options &options);
-  void set_ssh_options(const ssh::Ssh_connection_options &options);
+  void set_ssh_options(ssh::Ssh_connection_options &&options);
   const std::string &get_schema() const { return get_value(kSchema); }
   const std::string &get_socket() const { return get_value(kSocket); }
   const std::string &get_pipe() const { return get_value(kPipe); }
@@ -113,7 +113,7 @@ class SHCORE_PUBLIC Connection_options : public IConnection {
     return m_ssh_options;
   }
 
-  ssh::Ssh_connection_options &get_ssh_options_handle(int fallback_port = 0);
+  ssh::Ssh_connection_options &get_ssh_options() { return m_ssh_options; }
 
   const std::vector<std::string> &get_warnings() const { return m_warnings; }
   bool has_password() const override { return has_value(kPassword); }
diff --git a/mysqlshdk/libs/db/mysql/result.cc b/mysqlshdk/libs/db/mysql/result.cc
index d508f133b5..86ceaf2fde 100644
--- a/mysqlshdk/libs/db/mysql/result.cc
+++ b/mysqlshdk/libs/db/mysql/result.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2017, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -236,14 +236,13 @@ const IRow *Result::fetch_one() {
             if (code != 0) throw mysqlshdk::db::Error(err, code, state);
           }
 
-          // It means we are done, time to fetch the statement id
-          fetch_statement_id();
+          on_end_fetch();
         }
       } else {
         _row.reset();
       }
     } else {
-      fetch_statement_id();
+      on_end_fetch();
       _row.reset();
     }
     return _row.get();
@@ -252,6 +251,13 @@ const IRow *Result::fetch_one() {
   return nullptr;
 }
 
+void Result::on_end_fetch() {
+  if (auto s = _session.lock()) {
+    s->check_session_track_system_variables();
+  }
+  fetch_statement_id();
+}
+
 void Result::fetch_statement_id() {
   if (!m_statement_id.has_value()) {
     if (auto s = _session.lock()) {
diff --git a/mysqlshdk/libs/db/mysql/result.h b/mysqlshdk/libs/db/mysql/result.h
index 239c333d78..68a8db6baf 100644
--- a/mysqlshdk/libs/db/mysql/result.h
+++ b/mysqlshdk/libs/db/mysql/result.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2017, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -100,6 +100,8 @@ class SHCORE_PUBLIC Result : public mysqlshdk::db::IResult,
   void fetch_statement_id();
   Type map_data_type(int raw_type, int flags, int collation_id);
 
+  void on_end_fetch();
+
   virtual std::shared_ptr<Field_names> field_names() const;
 
   std::weak_ptr<mysqlshdk::db::mysql::Session_impl> _session;
diff --git a/mysqlshdk/libs/db/mysql/session.cc b/mysqlshdk/libs/db/mysql/session.cc
index 031b62d420..c9bdda5f46 100644
--- a/mysqlshdk/libs/db/mysql/session.cc
+++ b/mysqlshdk/libs/db/mysql/session.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2017, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -348,7 +348,8 @@ void Session_impl::connect(
   }
 
   if (connection_options.has(mysqlshdk::db::kLocalInfile)) {
-    const int local_infile = 1;
+    const int local_infile =
+        connection_options.is_enabled(mysqlshdk::db::kLocalInfile);
     mysql_options(_mysql, MYSQL_OPT_LOCAL_INFILE, &local_infile);
   }
 
@@ -554,6 +555,9 @@ std::shared_ptr<IResult> Session_impl::run_sql(
   if (_mysql == nullptr) throw std::runtime_error("Not connected");
   mysqlshdk::utils::Profile_timer timer;
   timer.stage_begin("run_sql");
+
+  m_session_tracker_info.reset();
+
   if (_prev_result) {
     _prev_result.reset();
   } else {
@@ -663,6 +667,8 @@ std::shared_ptr<IResult> Session_impl::run_sql(
       new Result(shared_from_this(), mysql_affected_rows(_mysql),
                  mysql_insert_id(_mysql), mysql_info(_mysql), buffered));
 
+  check_session_track_system_variables();
+
   /*
   Because of the way UDFs are implemented in the server, they don't return
   errors the same way "regular" functions do. In short, they have two parts: an
@@ -751,27 +757,53 @@ std::vector<std::string> Session_impl::get_last_gtids() const {
   return gtids;
 }
 
-std::optional<std::string> Session_impl::get_last_statement_id() const {
+namespace {
+enum class Tracked_system_variable { UNINTERESTING, STATEMENT_ID, SQL_MODE };
+
+Tracked_system_variable variable_by_name(const char *data, size_t length) {
+  if (strncmp(data, "statement_id", length) == 0)
+    return Tracked_system_variable::STATEMENT_ID;
+  if (strncmp(data, "sql_mode", length) == 0)
+    return Tracked_system_variable::SQL_MODE;
+  return Tracked_system_variable::UNINTERESTING;
+}
+}  // namespace
+
+void Session_impl::check_session_track_system_variables() const {
+  // Must be called after results are fetched, statement_id only
+  // comes in after that
   const char *data;
   size_t length;
-  std::optional<std::string> statement_id;
+  int found_variables = 0;
 
   if (mysql_session_track_get_first(_mysql, SESSION_TRACK_SYSTEM_VARIABLES,
                                     &data, &length) == 0) {
-    bool found_statement_id = strncmp(data, "statement_id", length) == 0;
+    Tracked_system_variable var = variable_by_name(data, length);
 
-    while (!statement_id.has_value() &&
-           mysql_session_track_get_next(_mysql, SESSION_TRACK_SYSTEM_VARIABLES,
+    while (mysql_session_track_get_next(_mysql, SESSION_TRACK_SYSTEM_VARIABLES,
                                         &data, &length) == 0) {
-      if (found_statement_id) {
-        statement_id = std::string(data, length);
-      } else {
-        found_statement_id = strncmp(data, "statement_id", length) == 0;
+      if (var == Tracked_system_variable::STATEMENT_ID) {
+        m_session_tracker_info.statement_id = std::string(data, length);
+        found_variables++;
+      } else if (var == Tracked_system_variable::SQL_MODE) {
+        m_session_tracker_info.sql_mode = std::string(data, length);
+        found_variables++;
       }
+      if (found_variables == 2) break;
+      if (mysql_session_track_get_next(_mysql, SESSION_TRACK_SYSTEM_VARIABLES,
+                                       &data, &length) != 0)
+        break;
+      var = variable_by_name(data, length);
     }
   }
+}
+
+const std::optional<std::string> &Session_impl::get_last_statement_id() const {
+  return m_session_tracker_info.statement_id;
+}
 
-  return statement_id;
+const std::optional<std::string> &Session_impl::get_last_sql_mode() const {
+  return m_session_tracker_info.sql_mode;
 }
 
 std::string Session::escape_string(std::string_view s) const {
@@ -825,6 +857,35 @@ std::shared_ptr<Session> Session::create() {
   return std::shared_ptr<Session>(new Session());
 }
 
+std::string Session::track_system_variable(const std::string &variable) {
+  if (get_server_version() < mysqlshdk::utils::Version(5, 7, 0))
+    throw std::invalid_argument("Session tracker not supported by server");
+
+  if (variable != "sql_mode")
+    throw std::invalid_argument("Variable unsupported");
+
+  const auto res = query("SELECT @@SESSION.session_track_system_variables");
+  const auto row = res->fetch_one_or_throw();
+
+  auto current_value = shcore::str_lower(row->get_string(0));
+
+  if (shcore::has_session_track_system_variable(current_value, variable))
+    return current_value;
+
+  if (!current_value.empty()) current_value.append(",");
+  current_value.append(variable);
+
+  executef("SET SESSION session_track_system_variables = ?", current_value);
+
+  return current_value;
+}
+
+void Session::refresh_sql_mode_tracked() {
+  if (_impl->m_session_tracker_info.sql_mode.has_value()) {
+    set_sql_mode(*_impl->m_session_tracker_info.sql_mode);
+  }
+}
+
 }  // namespace mysql
 }  // namespace db
 }  // namespace mysqlshdk
diff --git a/mysqlshdk/libs/db/mysql/session.h b/mysqlshdk/libs/db/mysql/session.h
index 552bc68d5d..448c727fa0 100644
--- a/mysqlshdk/libs/db/mysql/session.h
+++ b/mysqlshdk/libs/db/mysql/session.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2017, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -106,6 +106,12 @@ class Session_impl : public std::enable_shared_from_this<Session_impl> {
   Session_impl();
   void connect(const mysqlshdk::db::Connection_options &connection_info);
 
+  std::shared_ptr<IResult> query(
+      std::string_view sql, bool buffered,
+      const std::vector<Query_attribute> &query_attributes = {}) {
+    return query(sql.data(), sql.length(), buffered, query_attributes);
+  }
+
   std::shared_ptr<IResult> query(
       const char *sql, size_t len, bool buffered,
       const std::vector<Query_attribute> &query_attributes = {});
@@ -192,7 +198,8 @@ class Session_impl : public std::enable_shared_from_this<Session_impl> {
   }
 
   std::vector<std::string> get_last_gtids() const;
-  std::optional<std::string> get_last_statement_id() const;
+  const std::optional<std::string> &get_last_statement_id() const;
+  const std::optional<std::string> &get_last_sql_mode() const;
 
   uint32_t get_server_status() const {
     return _mysql ? _mysql->server_status : 0;
@@ -222,6 +229,8 @@ class Session_impl : public std::enable_shared_from_this<Session_impl> {
 
   void set_character_set(const std::string &charset_name);
 
+  void check_session_track_system_variables() const;
+
   std::string _uri;
   MYSQL *_mysql = nullptr;
   std::shared_ptr<MYSQL_RES> _prev_result;
@@ -229,6 +238,17 @@ class Session_impl : public std::enable_shared_from_this<Session_impl> {
   uint64_t m_thread_id = 0;
   std::unique_ptr<Error> m_last_error;
 
+  struct Session_tracker_info {
+    std::optional<std::string> statement_id;
+    std::optional<std::string> sql_mode;
+
+    void reset() {
+      statement_id.reset();
+      sql_mode.reset();
+    }
+  };
+  mutable Session_tracker_info m_session_tracker_info;
+
   struct Local_infile_callbacks {
     int (*init)(void **, const char *, void *) = nullptr;
     int (*read)(void *, char *, unsigned int) = nullptr;
@@ -255,16 +275,21 @@ class SHCORE_PUBLIC Session : public ISession,
   std::shared_ptr<IResult> querys(
       const char *sql, size_t len, bool buffered = false,
       const std::vector<Query_attribute> &query_attributes = {}) override {
-    return _impl->query(sql, len, buffered, query_attributes);
+    auto res = _impl->query(sql, len, buffered, query_attributes);
+    refresh_sql_mode_tracked();
+    return res;
   }
 
   std::shared_ptr<IResult> query_udf(std::string_view sql,
                                      bool buffered = false) override {
-    return _impl->query_udf(sql, buffered);
+    auto res = _impl->query_udf(sql, buffered);
+    refresh_sql_mode_tracked();
+    return res;
   }
 
   void executes(const char *sql, size_t len) override {
     _impl->execute(sql, len);
+    refresh_sql_mode_tracked();
   }
 
   const char *get_ssl_cipher() const override {
@@ -310,11 +335,13 @@ class SHCORE_PUBLIC Session : public ISession,
    */
   MYSQL *release_connection() { return _impl->release_connection(); }
 
+  std::string track_system_variable(const std::string &variable) override;
+
   /**
    * Sets the default character set for the current connection.
    *
-   * This function works like the SET NAMES statement, but also sets the value
-   * of mysql->charset.
+   * This function works like the SET NAMES statement, but also sets the
+   * value of mysql->charset.
    */
   void set_character_set(const std::string &charset_name) {
     _impl->set_character_set(charset_name);
@@ -376,6 +403,8 @@ class SHCORE_PUBLIC Session : public ISession,
 
   void do_close() override { _impl->close(); }
 
+  void refresh_sql_mode_tracked();
+
  private:
   std::shared_ptr<Session_impl> _impl;
 };
diff --git a/mysqlshdk/libs/db/mysqlx/session.h b/mysqlshdk/libs/db/mysqlx/session.h
index 3d3a430704..2d96c68966 100644
--- a/mysqlshdk/libs/db/mysqlx/session.h
+++ b/mysqlshdk/libs/db/mysqlx/session.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2017, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -285,6 +285,8 @@ class SHCORE_PUBLIC Session : public ISession,
     _impl->add_notice_listener(listener);
   }
 
+  std::string track_system_variable(const std::string &) override { return ""; }
+
  public:
   xcl::XSession *get_driver_obj() { return _impl->_mysql.get(); }
 
diff --git a/mysqlshdk/libs/db/mysqlx/xsession.cc b/mysqlshdk/libs/db/mysqlx/xsession.cc
index 9c482936fb..52bd36fcb7 100644
--- a/mysqlshdk/libs/db/mysqlx/xsession.cc
+++ b/mysqlshdk/libs/db/mysqlx/xsession.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2017, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -181,6 +181,12 @@ void XSession_impl::connect(const mysqlshdk::db::Connection_options &data) {
         "X Protocol: Option server-public-key-path is not supported.");
   }
 
+  if (_connection_options.has(mysqlshdk::db::kLocalInfile)) {
+    _mysql.reset();
+    throw std::runtime_error(
+        "X Protocol: LOAD DATA LOCAL INFILE is not supported.");
+  }
+
   auto &ssl_options(_connection_options.get_ssl_options());
 
   std::string ssl_mode;
diff --git a/mysqlshdk/libs/db/session.cc b/mysqlshdk/libs/db/session.cc
index 3a573a31fd..0f4b24828b 100644
--- a/mysqlshdk/libs/db/session.cc
+++ b/mysqlshdk/libs/db/session.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -37,29 +37,36 @@ Query_attribute::Query_attribute(
 
 void ISession::connect(const mysqlshdk::db::Connection_options &data) {
   do_connect(data);
+
   if (is_open()) {
-    const auto &ssh = data.get_ssh_options();
-    if (ssh.has_data()) {
-      mysqlshdk::ssh::current_ssh_manager()->port_usage_increment(ssh);
+    if (const auto &ssh = data.get_ssh_options(); ssh.has_data()) {
+      mysqlshdk::ssh::current_ssh_manager()->get_tunnel(ssh).ref();
     }
   }
 }
 
 void ISession::close() {
-  auto connection_options = get_connection_options();
-  bool was_open = is_open();
-  do_close();
-  if (was_open) {
-    try {
-      const auto &ssh = connection_options.get_ssh_options();
-      if (ssh.has_data()) {
-        mysqlshdk::ssh::current_ssh_manager()->port_usage_decrement(ssh);
-      }
-    } catch (const std::exception &ex) {
-      log_error("An error occurred while closing connection: %s", ex.what());
-      // no op
+  mysqlshdk::ssh::Ssh_tunnel tunnel;
+
+  if (is_open()) {
+    if (const auto &ssh = get_connection_options().get_ssh_options();
+        ssh.has_data()) {
+      // need to fetch the tunnel before closing the connection, as connection
+      // options could be reset as part of that step
+      tunnel = mysqlshdk::ssh::current_ssh_manager()->get_tunnel(ssh);
     }
   }
+
+  do_close();
+
+  tunnel.unref();
+}
+
+void ISession::set_sql_mode(const std::string &sql_mode) {
+  m_sql_mode = shcore::str_upper(sql_mode);
+  m_ansi_quotes_enabled = m_sql_mode->find("ANSI_QUOTES") != std::string::npos;
+  m_no_backslash_escapes_enabled =
+      m_sql_mode->find("NO_BACKSLASH_ESCAPES") != std::string::npos;
 }
 
 void ISession::refresh_sql_mode() {
@@ -70,12 +77,7 @@ void ISession::refresh_sql_mode() {
 
     if (!row || row->is_null(0)) throw std::runtime_error("Missing sql_mode");
 
-    m_sql_mode = shcore::str_upper(row->get_string(0));
-    m_ansi_quotes_enabled =
-        m_sql_mode->find("ANSI_QUOTES") != std::string::npos;
-    m_no_backslash_escapes_enabled =
-        m_sql_mode->find("NO_BACKSLASH_ESCAPES") != std::string::npos;
-
+    set_sql_mode(row->get_string(0));
   } catch (...) {
     m_sql_mode = std::nullopt;
     m_ansi_quotes_enabled = m_no_backslash_escapes_enabled = false;
diff --git a/mysqlshdk/libs/db/session.h b/mysqlshdk/libs/db/session.h
index 6eed543a5e..b673189c59 100644
--- a/mysqlshdk/libs/db/session.h
+++ b/mysqlshdk/libs/db/session.h
@@ -235,12 +235,16 @@ class SHCORE_PUBLIC ISession {
     return m_dollar_quoted_strings.value_or(false);
   }
 
+  virtual std::string track_system_variable(const std::string &) = 0;
+
   void refresh_sql_mode();
 
  protected:
   virtual void do_connect(const mysqlshdk::db::Connection_options &data) = 0;
   virtual void do_close() = 0;
 
+  void set_sql_mode(const std::string &sql_mode);
+
  private:
   std::optional<std::string> m_sql_mode;
   bool m_ansi_quotes_enabled = false;
diff --git a/mysqlshdk/libs/oci/CMakeLists.txt b/mysqlshdk/libs/oci/CMakeLists.txt
index 53dc888c56..082aa54b24 100644
--- a/mysqlshdk/libs/oci/CMakeLists.txt
+++ b/mysqlshdk/libs/oci/CMakeLists.txt
@@ -28,12 +28,14 @@ ENDIF()
 set(oci_SOURCES
   api_key_credentials_provider.cc
   config_credentials_provider.cc
+  config_file.cc
   data_plane_client.cc
   federation_client.cc
   federation_credentials_provider.cc
   fixed_credentials_provider.cc
   instance_metadata_retriever.cc
   instance_principal_credentials_provider.cc
+  instance_principal_delegation_token_credentials_provider.cc
   oci_bucket.cc
   oci_bucket_config.cc
   oci_bucket_options.cc
diff --git a/mysqlshdk/libs/oci/config_credentials_provider.cc b/mysqlshdk/libs/oci/config_credentials_provider.cc
index 4b0b143115..dc097f2fe0 100644
--- a/mysqlshdk/libs/oci/config_credentials_provider.cc
+++ b/mysqlshdk/libs/oci/config_credentials_provider.cc
@@ -146,120 +146,13 @@ Config_credentials_provider::Config_credentials_provider(
     const std::string &config_file, const std::string &config_profile,
     std::string name, bool allow_key_content_env_var)
     : Oci_credentials_provider(std::move(name)),
-      m_config_file(config_file),
-      m_config_profile(config_profile),
-      m_config(mysqlshdk::config::Case::SENSITIVE,
-               mysqlshdk::config::Escape::NO) {
-  log_info("Using the OCI config file '%s' and a profile named: '%s'",
-           m_config_file.c_str(), m_config_profile.c_str());
-
+      m_config(config_file, config_profile) {
   set_region(config_option(Entry::REGION));
   set_tenancy_id(config_option(Entry::TENANCY));
 
   load_key(allow_key_content_env_var);
 }
 
-std::string Config_credentials_provider::config_option(Entry entry) {
-  const char *env_var = nullptr;
-  const char *name = nullptr;
-  // if entry is optional, we don't report any errors, and simply return an
-  // empty string
-  bool optional = false;
-#define DONT_FAIL_IF_OPTIONAL \
-  do {                        \
-    if (optional) {           \
-      return {};              \
-    }                         \
-  } while (false)
-
-  switch (entry) {
-    case Entry::USER:
-      env_var = "OCI_CLI_USER";
-      name = "user";
-      break;
-
-    case Entry::FINGERPRINT:
-      env_var = "OCI_CLI_FINGERPRINT";
-      name = "fingerprint";
-      break;
-
-    case Entry::KEY_FILE:
-      env_var = "OCI_CLI_KEY_FILE";
-      name = "key_file";
-      break;
-
-    case Entry::PASS_PHRASE:
-      env_var = "OCI_CLI_PASSPHRASE";
-      name = "pass_phrase";
-      optional = true;
-      break;
-
-    case Entry::TENANCY:
-      env_var = "OCI_CLI_TENANCY";
-      name = "tenancy";
-      break;
-
-    case Entry::REGION:
-      env_var = "OCI_CLI_REGION";
-      name = "region";
-      break;
-
-    case Entry::SECURITY_TOKEN_FILE:
-      env_var = "OCI_CLI_SECURITY_TOKEN_FILE";
-      name = "security_token_file";
-      break;
-  }
-
-  assert(env_var && name);
-
-  if (const auto env = shcore::get_env(env_var); env.has_value()) {
-    log_info("The OCI config entry '%s' set via '%s' environment variable",
-             name, env_var);
-    return *env;
-  }
-
-  if (!m_config_is_read) {
-    try {
-      m_config.read(m_config_file);
-    } catch (const std::exception &e) {
-      DONT_FAIL_IF_OPTIONAL;
-      log_error("Failed to read the OCI config file '%s': %s",
-                m_config_file.c_str(), e.what());
-      throw std::runtime_error("Could not read the OCI config file.");
-    }
-
-    if (!m_config.has_group(m_config_profile)) {
-      DONT_FAIL_IF_OPTIONAL;
-      log_error(
-          "The OCI config file '%s' does not contain a profile named: '%s'",
-          m_config_file.c_str(), m_config_profile.c_str());
-      throw std::runtime_error("The indicated OCI Profile does not exist.");
-    }
-
-    m_config_is_read = true;
-  }
-
-  if (!m_config.has_option(m_config_profile, name)) {
-    DONT_FAIL_IF_OPTIONAL;
-    throw std::runtime_error(shcore::str_format(
-        "The OCI profile '%s' does not contain an entry named: '%s'",
-        m_config_profile.c_str(), name));
-  }
-
-  auto option = m_config.get(m_config_profile, name);
-
-  if (!option.has_value()) {
-    DONT_FAIL_IF_OPTIONAL;
-    throw std::runtime_error(shcore::str_format(
-        "The OCI profile '%s' has an entry '%s' with no value",
-        m_config_profile.c_str(), name));
-  }
-
-#undef DONT_FAIL_IF_OPTIONAL
-
-  return std::move(*option);
-}
-
 void Config_credentials_provider::load_key(bool allow_key_content_env_var) {
   static constexpr auto k_key_content_env_var = "OCI_CLI_KEY_CONTENT";
   shcore::ssl::Private_key_id key_id{""};
@@ -293,7 +186,7 @@ void Config_credentials_provider::load_key(bool allow_key_content_env_var) {
       throw std::runtime_error(
           shcore::str_format("Cannot load API key associated with OCI "
                              "configuration profile named '%s': %s",
-                             m_config_profile.c_str(), e.what()));
+                             config_profile().c_str(), e.what()));
     }
   }
 
diff --git a/mysqlshdk/libs/oci/config_credentials_provider.h b/mysqlshdk/libs/oci/config_credentials_provider.h
index 53a09bd6b6..652f06103d 100644
--- a/mysqlshdk/libs/oci/config_credentials_provider.h
+++ b/mysqlshdk/libs/oci/config_credentials_provider.h
@@ -28,9 +28,9 @@
 
 #include <string>
 
-#include "mysqlshdk/libs/config/config_file.h"
 #include "mysqlshdk/libs/utils/utils_private_key.h"
 
+#include "mysqlshdk/libs/oci/config_file.h"
 #include "mysqlshdk/libs/oci/oci_credentials_provider.h"
 
 namespace mysqlshdk {
@@ -50,36 +50,26 @@ class Config_credentials_provider : public Oci_credentials_provider {
       delete;
 
   inline const std::string &config_profile() const noexcept {
-    return m_config_profile;
+    return m_config.config_profile();
   }
 
  protected:
-  enum class Entry {
-    USER,
-    FINGERPRINT,
-    KEY_FILE,
-    PASS_PHRASE,
-    TENANCY,
-    REGION,
-    SECURITY_TOKEN_FILE,
-  };
+  using Entry = Config_file::Entry;
 
   Config_credentials_provider(const std::string &config_file,
                               const std::string &config_profile,
                               std::string name, bool allow_key_content_env_var);
 
-  std::string config_option(Entry entry);
+  inline std::string config_option(Entry entry) {
+    return m_config.config_option(entry);
+  }
 
   Credentials m_credentials;
 
  private:
   void load_key(bool allow_key_content_env_var);
 
-  std::string m_config_file;
-  std::string m_config_profile;
-
-  mysqlshdk::config::Config_file m_config;
-  bool m_config_is_read = false;
+  Config_file m_config;
 };
 
 }  // namespace oci
diff --git a/mysqlshdk/libs/oci/config_file.cc b/mysqlshdk/libs/oci/config_file.cc
new file mode 100644
index 0000000000..21be6c7400
--- /dev/null
+++ b/mysqlshdk/libs/oci/config_file.cc
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2024, 2025, Oracle and/or its affiliates.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2.0,
+ * as published by the Free Software Foundation.
+ *
+ * This program is designed to work with certain software (including
+ * but not limited to OpenSSL) that is licensed under separate terms,
+ * as designated in a particular file or component or in included license
+ * documentation.  The authors of MySQL hereby grant you an additional
+ * permission to link the program and your derivative works with the
+ * separately licensed software that they have either included with
+ * the program or referenced in the documentation.
+ *
+ * This program is distributed in the hope that it will be useful,  but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
+ * the GNU General Public License, version 2.0, for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "mysqlshdk/libs/oci/config_file.h"
+
+#include <cassert>
+#include <stdexcept>
+#include <string>
+#include <utility>
+
+#include "mysqlshdk/libs/utils/logger.h"
+#include "mysqlshdk/libs/utils/utils_general.h"
+#include "mysqlshdk/libs/utils/utils_path.h"
+
+namespace mysqlshdk {
+namespace oci {
+
+Config_file::Config_file(const std::string &config_file,
+                         const std::string &config_profile)
+    : m_config_file(shcore::path::expand_user(config_file)),
+      m_config_profile(config_profile),
+      m_config(mysqlshdk::config::Case::SENSITIVE,
+               mysqlshdk::config::Escape::NO) {
+  log_info("Using the OCI config file '%s' and a profile named: '%s'",
+           m_config_file.c_str(), m_config_profile.c_str());
+}
+
+std::string Config_file::config_option(Entry entry) {
+  const char *env_var = nullptr;
+  const char *name = nullptr;
+  // if entry is optional, we don't report any errors, and simply return an
+  // empty string
+  bool optional = false;
+#define DONT_FAIL_IF_OPTIONAL \
+  do {                        \
+    if (optional) {           \
+      return {};              \
+    }                         \
+  } while (false)
+
+  switch (entry) {
+    case Entry::USER:
+      env_var = "OCI_CLI_USER";
+      name = "user";
+      break;
+
+    case Entry::FINGERPRINT:
+      env_var = "OCI_CLI_FINGERPRINT";
+      name = "fingerprint";
+      break;
+
+    case Entry::KEY_FILE:
+      env_var = "OCI_CLI_KEY_FILE";
+      name = "key_file";
+      break;
+
+    case Entry::PASS_PHRASE:
+      env_var = "OCI_CLI_PASSPHRASE";
+      name = "pass_phrase";
+      optional = true;
+      break;
+
+    case Entry::TENANCY:
+      env_var = "OCI_CLI_TENANCY";
+      name = "tenancy";
+      break;
+
+    case Entry::REGION:
+      env_var = "OCI_CLI_REGION";
+      name = "region";
+      break;
+
+    case Entry::SECURITY_TOKEN_FILE:
+      env_var = "OCI_CLI_SECURITY_TOKEN_FILE";
+      name = "security_token_file";
+      break;
+
+    case Entry::DELEGATION_TOKEN_FILE:
+      name = "delegation_token_file";
+      break;
+  }
+
+  assert(name);
+
+  if (env_var) {
+    if (const auto env = shcore::get_env(env_var); env.has_value()) {
+      log_info("The OCI config entry '%s' set via '%s' environment variable",
+               name, env_var);
+      return *env;
+    }
+  }
+
+  if (!m_config_is_read) {
+    try {
+      m_config.read(m_config_file);
+    } catch (const std::exception &e) {
+      DONT_FAIL_IF_OPTIONAL;
+      log_error("Failed to read the OCI config file '%s': %s",
+                m_config_file.c_str(), e.what());
+      throw std::runtime_error("Could not read the OCI config file.");
+    }
+
+    if (!m_config.has_group(m_config_profile)) {
+      DONT_FAIL_IF_OPTIONAL;
+      log_error(
+          "The OCI config file '%s' does not contain a profile named: '%s'",
+          m_config_file.c_str(), m_config_profile.c_str());
+      throw std::runtime_error("The indicated OCI Profile does not exist.");
+    }
+
+    m_config_is_read = true;
+  }
+
+  if (!m_config.has_option(m_config_profile, name)) {
+    DONT_FAIL_IF_OPTIONAL;
+    throw std::runtime_error(shcore::str_format(
+        "The OCI profile '%s' does not contain an entry named: '%s'",
+        m_config_profile.c_str(), name));
+  }
+
+  auto option = m_config.get(m_config_profile, name);
+
+  if (!option.has_value()) {
+    DONT_FAIL_IF_OPTIONAL;
+    throw std::runtime_error(shcore::str_format(
+        "The OCI profile '%s' has an entry '%s' with no value",
+        m_config_profile.c_str(), name));
+  }
+
+#undef DONT_FAIL_IF_OPTIONAL
+
+  return std::move(*option);
+}
+
+}  // namespace oci
+}  // namespace mysqlshdk
diff --git a/mysqlshdk/libs/oci/config_file.h b/mysqlshdk/libs/oci/config_file.h
new file mode 100644
index 0000000000..74a08bba90
--- /dev/null
+++ b/mysqlshdk/libs/oci/config_file.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2024, 2025, Oracle and/or its affiliates.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2.0,
+ * as published by the Free Software Foundation.
+ *
+ * This program is designed to work with certain software (including
+ * but not limited to OpenSSL) that is licensed under separate terms,
+ * as designated in a particular file or component or in included license
+ * documentation.  The authors of MySQL hereby grant you an additional
+ * permission to link the program and your derivative works with the
+ * separately licensed software that they have either included with
+ * the program or referenced in the documentation.
+ *
+ * This program is distributed in the hope that it will be useful,  but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
+ * the GNU General Public License, version 2.0, for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef MYSQLSHDK_LIBS_OCI_CONFIG_FILE_H_
+#define MYSQLSHDK_LIBS_OCI_CONFIG_FILE_H_
+
+#include <string>
+
+#include "mysqlshdk/libs/config/config_file.h"
+
+namespace mysqlshdk {
+namespace oci {
+
+/**
+ * An OCI config file.
+ */
+class Config_file final {
+ public:
+  enum class Entry {
+    USER,
+    FINGERPRINT,
+    KEY_FILE,
+    PASS_PHRASE,
+    TENANCY,
+    REGION,
+    SECURITY_TOKEN_FILE,
+    DELEGATION_TOKEN_FILE,
+  };
+
+  Config_file(const std::string &config_file,
+              const std::string &config_profile);
+
+  Config_file(const Config_file &) = default;
+  Config_file(Config_file &&) = default;
+
+  Config_file &operator=(const Config_file &) = default;
+  Config_file &operator=(Config_file &&) = default;
+
+  ~Config_file() = default;
+
+  inline const std::string &config_file() const noexcept {
+    return m_config_file;
+  }
+
+  inline const std::string &config_profile() const noexcept {
+    return m_config_profile;
+  }
+
+  std::string config_option(Entry entry);
+
+ private:
+  std::string m_config_file;
+  std::string m_config_profile;
+
+  mysqlshdk::config::Config_file m_config;
+  bool m_config_is_read = false;
+};
+
+}  // namespace oci
+}  // namespace mysqlshdk
+
+#endif  // MYSQLSHDK_LIBS_OCI_CONFIG_FILE_H_
diff --git a/mysqlshdk/libs/oci/instance_principal_credentials_provider.cc b/mysqlshdk/libs/oci/instance_principal_credentials_provider.cc
index 2a965bb655..4b2da49b66 100644
--- a/mysqlshdk/libs/oci/instance_principal_credentials_provider.cc
+++ b/mysqlshdk/libs/oci/instance_principal_credentials_provider.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2024, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -29,6 +29,7 @@
 #include <cinttypes>
 #include <memory>
 #include <stdexcept>
+#include <string>
 #include <utility>
 
 #include "mysqlshdk/libs/rest/rest_service.h"
@@ -52,7 +53,11 @@ using shcore::ssl::Private_key;
 
 Instance_principal_credentials_provider::
     Instance_principal_credentials_provider()
-    : Oci_credentials_provider("instance_principal") {
+    : Instance_principal_credentials_provider("instance_principal") {}
+
+Instance_principal_credentials_provider::
+    Instance_principal_credentials_provider(std::string name)
+    : Oci_credentials_provider(std::move(name)) {
   try {
     m_instance_metadata = std::make_unique<Instance_metadata_retriever>();
     m_instance_metadata->refresh();
diff --git a/mysqlshdk/libs/oci/instance_principal_credentials_provider.h b/mysqlshdk/libs/oci/instance_principal_credentials_provider.h
index 5477fb796a..9f88684ad7 100644
--- a/mysqlshdk/libs/oci/instance_principal_credentials_provider.h
+++ b/mysqlshdk/libs/oci/instance_principal_credentials_provider.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2024, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -42,7 +42,7 @@ namespace oci {
 /**
  * Instance principal authentication.
  */
-class Instance_principal_credentials_provider final
+class Instance_principal_credentials_provider
     : public Oci_credentials_provider {
  public:
   Instance_principal_credentials_provider();
@@ -57,9 +57,14 @@ class Instance_principal_credentials_provider final
   Instance_principal_credentials_provider &operator=(
       Instance_principal_credentials_provider &&) = delete;
 
- private:
+  ~Instance_principal_credentials_provider() override = default;
+
+ protected:
+  explicit Instance_principal_credentials_provider(std::string name);
+
   Credentials fetch_credentials() override;
 
+ private:
   std::unique_ptr<Instance_metadata_retriever> m_instance_metadata;
   std::unique_ptr<Federation_credentials_provider> m_credentials_provider;
   std::unique_ptr<Federation_client> m_federation_client;
diff --git a/mysqlshdk/libs/oci/instance_principal_delegation_token_credentials_provider.cc b/mysqlshdk/libs/oci/instance_principal_delegation_token_credentials_provider.cc
new file mode 100644
index 0000000000..5b0e20154d
--- /dev/null
+++ b/mysqlshdk/libs/oci/instance_principal_delegation_token_credentials_provider.cc
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2024, 2025, Oracle and/or its affiliates.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2.0,
+ * as published by the Free Software Foundation.
+ *
+ * This program is designed to work with certain software (including
+ * but not limited to OpenSSL) that is licensed under separate terms,
+ * as designated in a particular file or component or in included license
+ * documentation.  The authors of MySQL hereby grant you an additional
+ * permission to link the program and your derivative works with the
+ * separately licensed software that they have either included with
+ * the program or referenced in the documentation.
+ *
+ * This program is distributed in the hope that it will be useful,  but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
+ * the GNU General Public License, version 2.0, for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "mysqlshdk/libs/oci/instance_principal_delegation_token_credentials_provider.h"
+
+#include <algorithm>
+#include <limits>
+#include <stdexcept>
+#include <string>
+
+#include "mysqlshdk/libs/utils/logger.h"
+#include "mysqlshdk/libs/utils/utils_file.h"
+#include "mysqlshdk/libs/utils/utils_path.h"
+#include "mysqlshdk/libs/utils/utils_string.h"
+
+#include "mysqlshdk/libs/oci/config_file.h"
+#include "mysqlshdk/libs/oci/security_token.h"
+
+namespace mysqlshdk {
+namespace oci {
+
+Instance_principal_delegation_token_credentials_provider::
+    Instance_principal_delegation_token_credentials_provider(
+        const std::string &config_file, const std::string &config_profile)
+    : Instance_principal_credentials_provider(
+          "instance_principal_with_delegation_token") {
+  auto config = Config_file{config_file, config_profile};
+  m_delegation_token_file = shcore::path::expand_user(
+      config.config_option(Config_file::Entry::DELEGATION_TOKEN_FILE));
+
+  if (!shcore::is_file(m_delegation_token_file)) {
+    throw std::runtime_error(shcore::str_format(
+        "The 'delegation_token_file' entry of the OCI profile '%s' is set to "
+        "'%s', which does not exist",
+        config_profile.c_str(), m_delegation_token_file.c_str()));
+  }
+
+  log_info("Using delegation_token_file: %s", m_delegation_token_file.c_str());
+}
+
+Instance_principal_delegation_token_credentials_provider::Credentials
+Instance_principal_delegation_token_credentials_provider::fetch_credentials() {
+  Security_token token;
+
+  try {
+    token = Security_token{
+        shcore::str_strip(shcore::get_text_file(m_delegation_token_file))};
+  } catch (const std::exception &e) {
+    throw std::runtime_error(
+        shcore::str_format("Failed to read the delegation token from '%s': %s",
+                           m_delegation_token_file.c_str(), e.what()));
+  }
+
+  auto credentials =
+      Instance_principal_credentials_provider::fetch_credentials();
+
+  // use the lower of the two expiration values
+  credentials.expiration = std::min(
+      credentials.expiration.value_or(std::numeric_limits<std::time_t>::max()),
+      token.expiration());
+
+  // add the token to the headers
+  credentials.extra_headers.emplace("opc-obo-token", token.to_string());
+
+  return credentials;
+}
+
+}  // namespace oci
+}  // namespace mysqlshdk
diff --git a/mysqlshdk/libs/oci/instance_principal_delegation_token_credentials_provider.h b/mysqlshdk/libs/oci/instance_principal_delegation_token_credentials_provider.h
new file mode 100644
index 0000000000..0b98614dc6
--- /dev/null
+++ b/mysqlshdk/libs/oci/instance_principal_delegation_token_credentials_provider.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2025, Oracle and/or its affiliates.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2.0,
+ * as published by the Free Software Foundation.
+ *
+ * This program is designed to work with certain software (including
+ * but not limited to OpenSSL) that is licensed under separate terms,
+ * as designated in a particular file or component or in included license
+ * documentation.  The authors of MySQL hereby grant you an additional
+ * permission to link the program and your derivative works with the
+ * separately licensed software that they have either included with
+ * the program or referenced in the documentation.
+ *
+ * This program is distributed in the hope that it will be useful,  but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
+ * the GNU General Public License, version 2.0, for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef MYSQLSHDK_LIBS_OCI_INSTANCE_PRINCIPAL_DELEGATION_TOKEN_CREDENTIALS_PROVIDER_H_
+#define MYSQLSHDK_LIBS_OCI_INSTANCE_PRINCIPAL_DELEGATION_TOKEN_CREDENTIALS_PROVIDER_H_
+
+#include <string>
+
+#include "mysqlshdk/libs/oci/instance_principal_credentials_provider.h"
+
+namespace mysqlshdk {
+namespace oci {
+
+/**
+ * Instance principal with delegation token authentication.
+ */
+class Instance_principal_delegation_token_credentials_provider final
+    : public Instance_principal_credentials_provider {
+ public:
+  Instance_principal_delegation_token_credentials_provider(
+      const std::string &config_file, const std::string &config_profile);
+
+  Instance_principal_delegation_token_credentials_provider(
+      const Instance_principal_delegation_token_credentials_provider &) =
+      delete;
+  Instance_principal_delegation_token_credentials_provider(
+      Instance_principal_delegation_token_credentials_provider &&) = delete;
+
+  Instance_principal_delegation_token_credentials_provider &operator=(
+      const Instance_principal_delegation_token_credentials_provider &) =
+      delete;
+  Instance_principal_delegation_token_credentials_provider &operator=(
+      Instance_principal_delegation_token_credentials_provider &&) = delete;
+
+  ~Instance_principal_delegation_token_credentials_provider() override =
+      default;
+
+ private:
+  Credentials fetch_credentials() override;
+
+  std::string m_delegation_token_file;
+};
+
+}  // namespace oci
+}  // namespace mysqlshdk
+
+#endif  // MYSQLSHDK_LIBS_OCI_INSTANCE_PRINCIPAL_DELEGATION_TOKEN_CREDENTIALS_PROVIDER_H_
diff --git a/mysqlshdk/libs/oci/oci_bucket_config.cc b/mysqlshdk/libs/oci/oci_bucket_config.cc
index 225de9dded..3bbe3febec 100644
--- a/mysqlshdk/libs/oci/oci_bucket_config.cc
+++ b/mysqlshdk/libs/oci/oci_bucket_config.cc
@@ -39,6 +39,7 @@
 
 #include "mysqlshdk/libs/oci/api_key_credentials_provider.h"
 #include "mysqlshdk/libs/oci/instance_principal_credentials_provider.h"
+#include "mysqlshdk/libs/oci/instance_principal_delegation_token_credentials_provider.h"
 #include "mysqlshdk/libs/oci/oci_bucket.h"
 #include "mysqlshdk/libs/oci/resource_principal_credentials_provider.h"
 #include "mysqlshdk/libs/oci/security_token_credentials_provider.h"
@@ -167,6 +168,12 @@ void Oci_bucket_config::resolve_credentials() {
           std::make_unique<Security_token_credentials_provider>(
               m_config_file, m_config_profile);
       break;
+
+    case Authentication::INSTANCE_OBO_USER:
+      m_credentials_provider = std::make_unique<
+          Instance_principal_delegation_token_credentials_provider>(
+          m_config_file, m_config_profile);
+      break;
   }
 
   m_credentials_provider->initialize();
diff --git a/mysqlshdk/libs/oci/oci_bucket_options.cc b/mysqlshdk/libs/oci/oci_bucket_options.cc
index 6c87043737..9586e57329 100644
--- a/mysqlshdk/libs/oci/oci_bucket_options.cc
+++ b/mysqlshdk/libs/oci/oci_bucket_options.cc
@@ -121,11 +121,13 @@ Authentication to_authentication(std::string_view auth) {
     return Authentication::INSTANCE_PRINCIPAL;
   } else if ("resource_principal" == auth) {
     return Authentication::RESOURCE_PRINCIPAL;
+  } else if ("instance_obo_user" == auth) {
+    return Authentication::INSTANCE_OBO_USER;
   } else {
-    throw std::invalid_argument(
-        shcore::str_format("expected one of: api_key, instance_principal, "
-                           "resource_principal, security_token, but got: %.*s",
-                           static_cast<int>(auth.length()), auth.data()));
+    throw std::invalid_argument(shcore::str_format(
+        "expected one of: api_key, instance_obo_user, instance_principal, "
+        "resource_principal, security_token, but got: %.*s",
+        static_cast<int>(auth.length()), auth.data()));
   }
 }
 
@@ -142,6 +144,9 @@ const char *to_string(Authentication auth) {
 
     case Authentication::RESOURCE_PRINCIPAL:
       return "resource_principal";
+
+    case Authentication::INSTANCE_OBO_USER:
+      return "instance_obo_user";
   }
 
   // should not happen
diff --git a/mysqlshdk/libs/oci/oci_bucket_options.h b/mysqlshdk/libs/oci/oci_bucket_options.h
index ff68cd226f..95aab60827 100644
--- a/mysqlshdk/libs/oci/oci_bucket_options.h
+++ b/mysqlshdk/libs/oci/oci_bucket_options.h
@@ -45,6 +45,7 @@ enum class Authentication {
   SECURITY_TOKEN,
   INSTANCE_PRINCIPAL,
   RESOURCE_PRINCIPAL,
+  INSTANCE_OBO_USER,
 };
 
 class Oci_bucket_options
diff --git a/mysqlshdk/libs/oci/oci_credentials.cc b/mysqlshdk/libs/oci/oci_credentials.cc
index cc1898c2f5..1eb42b125d 100644
--- a/mysqlshdk/libs/oci/oci_credentials.cc
+++ b/mysqlshdk/libs/oci/oci_credentials.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2024, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -32,12 +32,14 @@ namespace oci {
 
 Oci_credentials::Oci_credentials(std::string auth_key_id,
                                  shcore::ssl::Private_key_id private_key_id,
-                                 Time_point expiration)
+                                 Time_point expiration,
+                                 rest::Headers &&extra_headers)
     : Credentials(expiration),
       m_auth_key_id(std::move(auth_key_id)),
       m_private_key_id(std::move(private_key_id)),
       m_private_key(
-          shcore::ssl::Private_key_storage::instance().get(m_private_key_id)) {}
+          shcore::ssl::Private_key_storage::instance().get(m_private_key_id)),
+      m_extra_headers(std::move(extra_headers)) {}
 
 }  // namespace oci
 }  // namespace mysqlshdk
diff --git a/mysqlshdk/libs/oci/oci_credentials.h b/mysqlshdk/libs/oci/oci_credentials.h
index d4fe71ad7f..de8943e7a5 100644
--- a/mysqlshdk/libs/oci/oci_credentials.h
+++ b/mysqlshdk/libs/oci/oci_credentials.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2024, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -28,6 +28,7 @@
 
 #include <string>
 
+#include "mysqlshdk/libs/rest/headers.h"
 #include "mysqlshdk/libs/rest/signed/credentials.h"
 #include "mysqlshdk/libs/utils/utils_private_key.h"
 
@@ -44,7 +45,8 @@ class Oci_credentials final : public rest::Credentials<2> {
 
   Oci_credentials(std::string auth_key_id,
                   shcore::ssl::Private_key_id private_key_id,
-                  Time_point expiration = NO_EXPIRATION);
+                  Time_point expiration = NO_EXPIRATION,
+                  rest::Headers &&extra_headers = {});
 
   Oci_credentials(const Oci_credentials &) = default;
   Oci_credentials(Oci_credentials &&) = default;
@@ -68,10 +70,15 @@ class Oci_credentials final : public rest::Credentials<2> {
     return m_private_key;
   }
 
+  inline const rest::Headers &extra_headers() const noexcept {
+    return m_extra_headers;
+  }
+
  private:
   std::string m_auth_key_id;
   shcore::ssl::Private_key_id m_private_key_id;
   shcore::ssl::Private_key m_private_key;
+  rest::Headers m_extra_headers;
 };
 
 }  // namespace oci
diff --git a/mysqlshdk/libs/oci/oci_credentials_provider.cc b/mysqlshdk/libs/oci/oci_credentials_provider.cc
index 3f0484ad40..ea83a8b659 100644
--- a/mysqlshdk/libs/oci/oci_credentials_provider.cc
+++ b/mysqlshdk/libs/oci/oci_credentials_provider.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2024, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -51,7 +51,7 @@ void Oci_credentials_provider::set_tenancy_id(std::string tenancy_id) {
 Oci_credentials_provider::Credentials_ptr_t
 Oci_credentials_provider_traits::convert(
     const Oci_credentials_provider &self,
-    const Intermediate_credentials &credentials) {
+    Intermediate_credentials &&credentials) {
   // ensure that these are set
   assert(!credentials.auth_key_id.empty());
   assert(!credentials.private_key_id.empty());
@@ -70,8 +70,9 @@ Oci_credentials_provider_traits::convert(
   }
 
   return std::make_shared<Oci_credentials>(
-      credentials.auth_key_id,
-      shcore::ssl::Private_key_id{credentials.private_key_id}, expiration);
+      std::move(credentials.auth_key_id),
+      shcore::ssl::Private_key_id{std::move(credentials.private_key_id)},
+      std::move(expiration), std::move(credentials.extra_headers));
 }
 
 }  // namespace oci
diff --git a/mysqlshdk/libs/oci/oci_credentials_provider.h b/mysqlshdk/libs/oci/oci_credentials_provider.h
index 724fcc5ffe..7144cb70e3 100644
--- a/mysqlshdk/libs/oci/oci_credentials_provider.h
+++ b/mysqlshdk/libs/oci/oci_credentials_provider.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2024, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -32,6 +32,7 @@
 #include <optional>
 #include <string>
 
+#include "mysqlshdk/libs/rest/headers.h"
 #include "mysqlshdk/libs/rest/signed/credentials_provider.h"
 
 #include "mysqlshdk/libs/oci/oci_credentials.h"
@@ -49,10 +50,11 @@ struct Oci_credentials_provider_traits {
     std::string auth_key_id;
     std::string private_key_id;
     std::optional<std::time_t> expiration{};
+    rest::Headers extra_headers{};
   };
 
   static std::shared_ptr<Credentials_t> convert(
-      const Provider_t &self, const Intermediate_credentials &credentials);
+      const Provider_t &self, Intermediate_credentials &&credentials);
 };
 
 class Oci_credentials_provider
diff --git a/mysqlshdk/libs/oci/oci_signer.cc b/mysqlshdk/libs/oci/oci_signer.cc
index 9f640cc50f..f564bc986a 100644
--- a/mysqlshdk/libs/oci/oci_signer.cc
+++ b/mysqlshdk/libs/oci/oci_signer.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -25,6 +25,7 @@
 
 #include "mysqlshdk/libs/oci/oci_signer.h"
 
+#include <string>
 #include <string_view>
 #include <utility>
 
@@ -38,7 +39,7 @@ namespace oci {
 
 namespace {
 
-const std::string k_empty_payload_base64 =
+constexpr std::string_view k_empty_payload_base64 =
     "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=";
 
 std::string sign(EVP_PKEY *sigkey, const std::string &string_to_sign) {
@@ -97,42 +98,78 @@ rest::Headers Oci_signer::sign_request(const rest::Signed_request &request,
   const auto method = request.type;
   const auto &headers = request.unsigned_headers();
 
-  const auto date = mysqlshdk::utils::fmttime(
-      "%a, %d %b %Y %H:%M:%S GMT", mysqlshdk::utils::Time_type::GMT, &now);
+  auto date = mysqlshdk::utils::fmttime("%a, %d %b %Y %H:%M:%S GMT",
+                                        mysqlshdk::utils::Time_type::GMT, &now);
 
-  std::string string_to_sign{
-      "(request-target): " + mysqlshdk::rest::type_name(method) + " " + path +
-      "\nhost: " + m_host + "\nx-date: " + date};
+  std::string string_to_sign;
+  string_to_sign.reserve(512);
+
+  string_to_sign += "(request-target): ";
+  string_to_sign += mysqlshdk::rest::type_name(method);
+  string_to_sign += ' ';
+  string_to_sign += path;
+
+  string_to_sign += "\nhost: ";
+  string_to_sign += m_host;
+
+  string_to_sign += "\nx-date: ";
+  string_to_sign += date;
 
   // Sets the content type to application/json if no other specified
-  if (headers.find("content-type") != headers.end())
-    all_headers["content-type"] = headers.at("content-type");
-  else
+  if (const auto content_type = headers.find("content-type");
+      content_type != headers.end()) {
+    all_headers["content-type"] = content_type->second;
+  } else {
     all_headers["content-type"] = "application/json";
+  }
 
   if (method == rest::Type::POST) {
     all_headers["x-content-sha256"] =
         request.size ? encode_sha256({request.body, request.size})
                      : k_empty_payload_base64;
     all_headers["content-length"] = std::to_string(request.size);
-    string_to_sign.append(
-        "\nx-content-sha256: " + all_headers["x-content-sha256"] +
-        "\ncontent-length: " + all_headers["content-length"] +
-        "\ncontent-type: " + all_headers["content-type"]);
+
+    string_to_sign += "\nx-content-sha256: ";
+    string_to_sign += all_headers["x-content-sha256"];
+
+    string_to_sign += "\ncontent-length: ";
+    string_to_sign += all_headers["content-length"];
+
+    string_to_sign += "\ncontent-type: ";
+    string_to_sign += all_headers["content-type"];
   }
 
-  const std::string signature_b64 =
-      sign(m_credentials->private_key().ptr(), string_to_sign);
-  std::string auth_header =
-      "Signature version=\"1\",headers=\"(request-target) host x-date";
+  std::string auth_header;
+  auth_header.reserve(256);
+
+  auth_header +=
+      "Signature version=\"1\","
+      "algorithm=\"rsa-sha256\","
+      "headers=\"(request-target) host x-date";
 
   if (method == rest::Type::POST) {
     auth_header.append(" x-content-sha256 content-length content-type");
   }
 
-  auth_header.append("\",keyId=\"" + m_credentials->auth_key_id() +
-                     "\",algorithm=\"rsa-sha256\",signature=\"" +
-                     signature_b64 + "\"");
+  for (const auto &extra : m_credentials->extra_headers()) {
+    all_headers[extra.first] = extra.second;
+
+    string_to_sign += '\n';
+    string_to_sign += extra.first;
+    string_to_sign += ": ";
+    string_to_sign += extra.second;
+
+    auth_header += ' ';
+    auth_header += extra.first;
+  }
+
+  auth_header += "\",keyId=\"";
+  auth_header += m_credentials->auth_key_id();
+
+  auth_header += "\",signature=\"";
+  auth_header += sign(m_credentials->private_key().ptr(), string_to_sign);
+
+  auth_header += '"';
 
   all_headers["authorization"] = std::move(auth_header);
   all_headers["x-date"] = std::move(date);
diff --git a/mysqlshdk/libs/parser/mysql_parser_utils.cc b/mysqlshdk/libs/parser/mysql_parser_utils.cc
index 7bd5a039da..32b0d9e497 100644
--- a/mysqlshdk/libs/parser/mysql_parser_utils.cc
+++ b/mysqlshdk/libs/parser/mysql_parser_utils.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -58,8 +58,11 @@ class Parser_error_listener : public antlr4::BaseErrorListener {
                    antlr4::Token *offendingSymbol, size_t line,
                    size_t charPositionInLine, const std::string &msg,
                    std::exception_ptr /* e */) override {
-    throw Sql_syntax_error(msg, std::string(offendingSymbol->getText()), line,
-                           charPositionInLine);
+    throw Sql_syntax_error(msg,
+                           offendingSymbol
+                               ? std::string(offendingSymbol->getText())
+                               : std::string{},
+                           line, charPositionInLine);
   }
 };
 
@@ -176,6 +179,59 @@ class Parser_context {
   const antlr4::dfa::Vocabulary &m_vocabulary;
 };
 
+class Tokenizer_context {
+ public:
+  Tokenizer_context(std::string_view stmt, const Parser_config &config)
+      : m_input(stmt),
+        m_lexer(&m_input),
+        m_rule_names(m_lexer.getRuleNames()),
+        m_vocabulary(m_lexer.getVocabulary()) {
+    configure(config);
+
+    m_lexer.removeErrorListeners();
+    m_lexer.addErrorListener(&m_error_listener);
+  }
+
+  bool traverse(
+      const std::function<bool(std::string_view, std::string_view)> &visitor) {
+    for (;;) {
+      auto token = m_lexer.nextToken();
+      if (token->getType() == antlr4::Token::EOF) break;
+
+      if (!visitor(m_vocabulary.getSymbolicName(token->getType()),
+                   token->getText()))
+        return false;
+    }
+    return true;
+  }
+
+ private:
+  void configure(const Parser_config &config) {
+    using SqlMode = parsers::MySQLRecognizerCommon::SqlMode;
+
+    SqlMode mode = SqlMode::NoMode;
+
+    // TODO(alfredo) stop forcing ansi_quotes when BUG#37018247 is fixed
+    if (config.ansi_quotes || true) {
+      mode = SqlMode(mode | SqlMode::AnsiQuotes);
+    }
+
+    if (config.no_backslash_escapes) {
+      mode = SqlMode(mode | SqlMode::NoBackslashEscapes);
+    }
+
+    m_lexer.sqlMode = mode;
+    m_lexer.serverVersion =
+        (config.mysql_version ? config.mysql_version : utils::k_shell_version)
+            .numeric();
+  }
+
+  ANTLRInputStream m_input;
+  MySQLLexer m_lexer;
+  const std::vector<std::string> &m_rule_names;
+  const antlr4::dfa::Vocabulary &m_vocabulary;
+  Parser_error_listener m_error_listener;
+};
 }  // namespace
 
 void traverse_statement_ast(std::string_view stmt, const Parser_config &config,
@@ -220,6 +276,14 @@ void check_sql_syntax(const std::string &script, const Parser_config &config) {
   traverse_script_ast(script, config, {}, nullptr);
 }
 
+bool tokenize_statement(
+    std::string_view stmt, const Parser_config &config,
+    const std::function<bool(std::string_view, std::string_view)> &visitor) {
+  Tokenizer_context context{stmt, config};
+
+  return context.traverse(visitor);
+}
+
 bool Table_reference::operator==(const Table_reference &other) const {
   return schema == other.schema && table == other.table;
 }
diff --git a/mysqlshdk/libs/parser/mysql_parser_utils.h b/mysqlshdk/libs/parser/mysql_parser_utils.h
index 09474ef914..a7d282d1d4 100644
--- a/mysqlshdk/libs/parser/mysql_parser_utils.h
+++ b/mysqlshdk/libs/parser/mysql_parser_utils.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -143,6 +143,10 @@ void traverse_script_ast(
 void check_sql_syntax(const std::string &script,
                       const Parser_config &config = {});
 
+bool tokenize_statement(
+    std::string_view stmt, const Parser_config &config,
+    const std::function<bool(std::string_view, std::string_view)> &visitor);
+
 struct Table_reference {
   std::string schema;
   std::string table;
diff --git a/mysqlshdk/libs/rest/signed/credentials_provider.h b/mysqlshdk/libs/rest/signed/credentials_provider.h
index 7644c885f0..a88ebb4a24 100644
--- a/mysqlshdk/libs/rest/signed/credentials_provider.h
+++ b/mysqlshdk/libs/rest/signed/credentials_provider.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -48,7 +48,7 @@ namespace rest {
  *  - Intermediate_credentials - a structure to store the intermediate
  *    credentials
  *  - static std::shared_ptr<Credentials_t> convert(
- *        const Provider_t &self, const Intermediate_credentials &credentials) -
+ *        const Provider_t &self, Intermediate_credentials &&credentials) -
  *      a method to convert intermediate credentials into the final ones
  *
  * Traits::Provider_t is expected to contain:
@@ -159,7 +159,8 @@ class Credentials_provider {
       throw Credentials_error{e.what()};
     }
 
-    return Traits::convert(*static_cast<Provider_t *>(this), credentials);
+    return Traits::convert(*static_cast<Provider_t *>(this),
+                           std::move(credentials));
   }
 
   void maybe_refresh() {
diff --git a/mysqlshdk/libs/secret-store-api/helper_interface.cc b/mysqlshdk/libs/secret-store-api/helper_interface.cc
index 4d2759b128..48e79ea40e 100644
--- a/mysqlshdk/libs/secret-store-api/helper_interface.cc
+++ b/mysqlshdk/libs/secret-store-api/helper_interface.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -29,6 +29,7 @@
 #include <rapidjson/writer.h>
 
 #include <set>
+#include <utility>
 #include <vector>
 
 #include "mysql-secret-store/include/helper.h"
@@ -51,11 +52,14 @@ namespace {
 
 constexpr auto k_secret = "Secret";
 constexpr auto k_secret_type = "SecretType";
+constexpr auto k_secret_id = "SecretID";
+// TODO(pawel): this is deprecated, remove it at some point
 constexpr auto k_server_url = "ServerURL";
 
 constexpr auto k_secret_type_password = "password";
+constexpr auto k_secret_type_generic = "generic";
 
-void throw_json_error(const std::string &msg) {
+[[noreturn]] void throw_json_error(const std::string &msg) {
   throw std::runtime_error{"Failed to parse JSON output: " + msg};
 }
 
@@ -135,19 +139,13 @@ std::string validate_url(const std::string &url) {
   return options.as_uri(tokens);
 }
 
-void validate_secret(Secret_type type, const std::string &secret) {
-  if (Secret_type::PASSWORD == type) {
-    if (std::string::npos != secret.find_first_of("\r\n")) {
-      throw std::runtime_error{
-          "Unable to store password with a newline character"};
-    }
-  }
-}
-
 std::string to_string(Secret_type type) {
   switch (type) {
     case Secret_type::PASSWORD:
       return k_secret_type_password;
+
+    case Secret_type::GENERIC:
+      return k_secret_type_generic;
   }
 
   throw std::runtime_error{"Unknown secret type"};
@@ -158,8 +156,12 @@ Secret_type to_secret_type(const std::string &type) {
     return Secret_type::PASSWORD;
   }
 
+  if (type == k_secret_type_generic) {
+    return Secret_type::GENERIC;
+  }
+
   throw_json_error("Unknown secret type: \"" + type + "\"");
-  // to silence compiler complains
+  // to silence compiler's complains
   return Secret_type::PASSWORD;
 }
 
@@ -170,21 +172,24 @@ std::string to_string(rapidjson::Document *doc) {
   return std::string{buffer.GetString(), buffer.GetSize()};
 }
 
-void validate_object(const rapidjson::Value &object,
-                     const std::set<std::string> &members) {
+void validate_object(const rapidjson::Value &object) {
   if (!object.IsObject()) {
     throw_json_error("Expected object");
   }
+}
 
-  for (const auto &m : members) {
-    if (!object.HasMember(m.c_str())) {
-      throw_json_error("Missing member: \"" + m + "\"");
-    }
+std::string required(const rapidjson::Value &object, const char *name) {
+  const auto it = object.FindMember(name);
 
-    if (!object[m.c_str()].IsString()) {
-      throw_json_error("Member \"" + m + "\" is not a string");
-    }
+  if (object.MemberEnd() == it) {
+    throw_json_error("Missing member: \"" + std::string(name) + "\"");
+  }
+
+  if (!it->value.IsString()) {
+    throw_json_error("Member \"" + std::string(name) + "\" is not a string");
   }
+
+  return std::string{it->value.GetString(), it->value.GetStringLength()};
 }
 
 rapidjson::Document parse(const std::string &json) {
@@ -199,19 +204,37 @@ rapidjson::Document parse(const std::string &json) {
   return doc;
 }
 
-rapidjson::Document to_object(const Secret_spec &spec) {
+rapidjson::Document to_object(Secret_type type) {
   rapidjson::Document doc{rapidjson::Type::kObjectType};
   auto &allocator = doc.GetAllocator();
 
   doc.AddMember(rapidjson::StringRef(k_secret_type),
-                {to_string(spec.type).c_str(), allocator}, allocator);
+                {to_string(type).c_str(), allocator}, allocator);
+
+  return doc;
+}
+
+rapidjson::Document to_object(const Secret_spec &spec) {
+  const auto &spec_id =
+      Secret_type::PASSWORD == spec.type ? validate_url(spec.id) : spec.id;
+
+  auto doc = to_object(spec.type);
+  auto &allocator = doc.GetAllocator();
+
+  doc.AddMember(rapidjson::StringRef(k_secret_id), {spec_id.c_str(), allocator},
+                allocator);
   doc.AddMember(rapidjson::StringRef(k_server_url),
-                {validate_url(spec.url).c_str(), allocator}, allocator);
+                {spec_id.c_str(), allocator}, allocator);
 
   return doc;
 }
 
-std::string to_string(const Secret_spec &spec, const std::string &secret) {
+std::string to_json(Secret_type type) {
+  auto doc = to_object(type);
+  return to_string(&doc);
+}
+
+std::string to_json(const Secret_spec &spec, const std::string &secret) {
   auto doc = to_object(spec);
   auto &allocator = doc.GetAllocator();
 
@@ -222,21 +245,30 @@ std::string to_string(const Secret_spec &spec, const std::string &secret) {
   return to_string(&doc);
 }
 
-std::string to_string(const Secret_spec &spec) {
+std::string to_json(const Secret_spec &spec) {
   auto doc = to_object(spec);
   return to_string(&doc);
 }
 
 Secret_spec to_secret_spec(const rapidjson::Value &spec) {
-  validate_object(spec, {k_secret_type, k_server_url});
+  validate_object(spec);
 
-  return {to_secret_type(spec[k_secret_type].GetString()),
-          spec[k_server_url].GetString()};
+  std::string id;
+
+  // we first check for the deprecated key, to have a meaningful message if the
+  // new key is missing
+  try {
+    id = required(spec, k_server_url);
+  } catch (...) {
+    id = required(spec, k_secret_id);
+  }
+
+  return {to_secret_type(required(spec, k_secret_type)), std::move(id)};
 }
 
 void to_secret_spec_list(const std::string &spec,
                          std::vector<Secret_spec> *list) {
-  auto doc = parse(spec);
+  const auto doc = parse(spec);
 
   if (!doc.IsArray()) {
     throw_json_error("Expected array");
@@ -248,11 +280,11 @@ void to_secret_spec_list(const std::string &spec,
 }
 
 std::pair<Secret_spec, std::string> to_secret(const std::string &secret) {
-  auto doc = parse(secret);
+  const auto doc = parse(secret);
 
-  validate_object(doc, {k_secret});
+  validate_object(doc);
 
-  return std::make_pair(to_secret_spec(doc), doc[k_secret].GetString());
+  return std::make_pair(to_secret_spec(doc), required(doc, k_secret));
 }
 
 }  // namespace
@@ -266,10 +298,8 @@ class Helper_interface::Helper_interface_impl {
 
   bool store(const Secret_spec &spec, const std::string &secret) noexcept {
     try {
-      validate_secret(spec.type, secret);
-
       std::string output;
-      bool ret = m_invoker.store(to_string(spec, secret), &output);
+      bool ret = m_invoker.store(to_json(spec, secret), &output);
 
       if (ret) {
         clear_last_error();
@@ -292,7 +322,7 @@ class Helper_interface::Helper_interface_impl {
 
     try {
       std::string output;
-      bool ret = m_invoker.get(to_string(spec), &output);
+      bool ret = m_invoker.get(to_json(spec), &output);
 
       if (ret) {
         *secret = to_secret(output).second;
@@ -311,7 +341,7 @@ class Helper_interface::Helper_interface_impl {
   bool erase(const Secret_spec &spec) noexcept {
     try {
       std::string output;
-      bool ret = m_invoker.erase(to_string(spec), &output);
+      bool ret = m_invoker.erase(to_json(spec), &output);
 
       if (ret) {
         clear_last_error();
@@ -326,15 +356,22 @@ class Helper_interface::Helper_interface_impl {
     }
   }
 
-  bool list(std::vector<Secret_spec> *specs) const noexcept {
+  bool list(std::vector<Secret_spec> *specs,
+            std::optional<Secret_type> type = {}) const noexcept {
     if (nullptr == specs) {
       set_last_error("Invalid pointer");
       return false;
     }
 
     try {
+      std::string input;
+
+      if (type.has_value()) {
+        input = to_json(*type);
+      }
+
       std::string output;
-      bool ret = m_invoker.list(&output);
+      bool ret = m_invoker.list(&output, input);
 
       if (ret) {
         to_secret_spec_list(output, specs);
@@ -382,8 +419,9 @@ bool Helper_interface::erase(const Secret_spec &spec) noexcept {
   return m_impl->erase(spec);
 }
 
-bool Helper_interface::list(std::vector<Secret_spec> *specs) const noexcept {
-  return m_impl->list(specs);
+bool Helper_interface::list(std::vector<Secret_spec> *specs,
+                            std::optional<Secret_type> type) const noexcept {
+  return m_impl->list(specs, type);
 }
 
 std::string Helper_interface::get_last_error() const noexcept {
diff --git a/mysqlshdk/libs/secret-store-api/helper_invoker.cc b/mysqlshdk/libs/secret-store-api/helper_invoker.cc
index 18c487fe81..aa63610cd3 100644
--- a/mysqlshdk/libs/secret-store-api/helper_invoker.cc
+++ b/mysqlshdk/libs/secret-store-api/helper_invoker.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -81,8 +81,8 @@ bool Helper_invoker::erase(const std::string &input,
   return invoke("erase", input, output);
 }
 
-bool Helper_invoker::list(std::string *output) const {
-  return invoke("list", {}, output);
+bool Helper_invoker::list(std::string *output, const std::string &input) const {
+  return invoke("list", input, output);
 }
 
 bool Helper_invoker::version() const {
@@ -110,9 +110,10 @@ bool Helper_invoker::invoke(const char *command, const std::string &input,
 
     if (!input.empty()) {
       app.write(input.c_str(), input.length());
-      app.finish_writing();
     }
 
+    app.finish_writing();
+
     *output = shcore::str_strip(app.read_all());
 
     const auto exit_code = app.wait();
diff --git a/mysqlshdk/libs/secret-store-api/helper_invoker.h b/mysqlshdk/libs/secret-store-api/helper_invoker.h
index f7dfabb21b..443abc26ec 100644
--- a/mysqlshdk/libs/secret-store-api/helper_invoker.h
+++ b/mysqlshdk/libs/secret-store-api/helper_invoker.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -50,7 +50,7 @@ class Helper_invoker {
 
   bool erase(const std::string &input, std::string *output) const;
 
-  bool list(std::string *output) const;
+  bool list(std::string *output, const std::string &input = {}) const;
 
   bool version() const;
 
diff --git a/mysqlshdk/libs/secret-store-api/secret_spec.cc b/mysqlshdk/libs/secret-store-api/secret_spec.cc
index 93931d28e3..da59b82092 100644
--- a/mysqlshdk/libs/secret-store-api/secret_spec.cc
+++ b/mysqlshdk/libs/secret-store-api/secret_spec.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -30,7 +30,7 @@ namespace secret_store {
 namespace api {
 
 bool Secret_spec::operator==(const Secret_spec &r) const noexcept {
-  return type == r.type && url == r.url;
+  return type == r.type && id == r.id;
 }
 
 bool Secret_spec::operator!=(const Secret_spec &r) const noexcept {
diff --git a/mysqlshdk/libs/ssh/ssh_connection_options.cc b/mysqlshdk/libs/ssh/ssh_connection_options.cc
index 46f31dc06d..6dd5a91906 100644
--- a/mysqlshdk/libs/ssh/ssh_connection_options.cc
+++ b/mysqlshdk/libs/ssh/ssh_connection_options.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -151,6 +151,8 @@ void Ssh_connection_options::dump_config() const {
   log_debug2("SSH: remote host: %s", get_remote_host().c_str());
   if (m_remote_port.has_value())
     log_debug2("SSH remote port: %d", *m_remote_port);
+  if (m_fallback_remote_port.has_value())
+    log_debug2("SSH fallback remote port: %d", *m_fallback_remote_port);
   log_debug2("SSH: remote ssh host: %s", get_host().c_str());
   if (has_port()) {
     log_debug2("SSH: remote ssh port: %d", get_port());
diff --git a/mysqlshdk/libs/ssh/ssh_connection_options.h b/mysqlshdk/libs/ssh/ssh_connection_options.h
index 728a1a350e..39809e880f 100644
--- a/mysqlshdk/libs/ssh/ssh_connection_options.h
+++ b/mysqlshdk/libs/ssh/ssh_connection_options.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -70,6 +70,8 @@ class Ssh_connection_options : public mysqlshdk::IConnection {
     }
   }
 
+  void set_fallback_remote_port(int port) { m_fallback_remote_port = port; }
+
   void set_local_port(int port) {
     if (!m_local_port.has_value()) {
       m_local_port = port;
@@ -93,6 +95,7 @@ class Ssh_connection_options : public mysqlshdk::IConnection {
 
   void clear_remote_host() { clear_value(mysqlshdk::db::kSshRemoteHost); }
   void clear_remote_port() { m_remote_port.reset(); }
+  void clear_fallback_remote_port() { m_fallback_remote_port.reset(); }
   void clear_local_port() { m_local_port.reset(); }
   void clear_config_file() { clear_value(mysqlshdk::db::kSshConfigFile); }
   void clear_key_file() { clear_value(mysqlshdk::db::kSshIdentityFile); }
@@ -101,7 +104,10 @@ class Ssh_connection_options : public mysqlshdk::IConnection {
   virtual bool has_remote_host() const {
     return has_value(mysqlshdk::db::kSshRemoteHost);
   }
-  virtual bool has_remote_port() const { return m_remote_port.has_value(); }
+  virtual bool has_remote_port() const {
+    return m_remote_port.has_value() || m_fallback_remote_port.has_value();
+  }
+
   virtual bool has_local_port() const { return m_local_port.has_value(); }
 
   bool has_key_file() const {
@@ -123,16 +129,16 @@ class Ssh_connection_options : public mysqlshdk::IConnection {
   /**
    * The port of the remote service that will be forwarded.
    */
-  const int &get_remote_port() const {
-    if (!m_remote_port.has_value()) {
+  int get_remote_port() const {
+    if (!m_remote_port.has_value() && !m_fallback_remote_port.has_value()) {
       throw std::runtime_error(
           "Internal option 'ssh-remote-port' has no value.");
     }
 
-    return *m_remote_port;
+    return m_remote_port.value_or(m_fallback_remote_port.value_or(0));
   }
 
-  const int &get_local_port() const {
+  int get_local_port() const {
     if (!m_local_port.has_value()) {
       throw std::runtime_error(
           "Internal option 'ssh-local-port' has no value.");
@@ -202,6 +208,7 @@ class Ssh_connection_options : public mysqlshdk::IConnection {
   void check_key_encryption(const std::string &path);
 
   std::optional<int> m_remote_port;
+  std::optional<int> m_fallback_remote_port;
   std::optional<int> m_local_port;
   std::size_t m_connection_timeout = 10;
   std::string m_sourcehost = "127.0.0.1";
diff --git a/mysqlshdk/libs/ssh/ssh_manager.cc b/mysqlshdk/libs/ssh/ssh_manager.cc
index 56935faf28..4869f0dadd 100644
--- a/mysqlshdk/libs/ssh/ssh_manager.cc
+++ b/mysqlshdk/libs/ssh/ssh_manager.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -24,8 +24,11 @@
  */
 
 #include "mysqlshdk/libs/ssh/ssh_manager.h"
+
 #include <string>
+#include <utility>
 #include <vector>
+
 #include "mysqlshdk/include/scripting/shexcept.h"
 #include "mysqlshdk/libs/ssh/ssh_session.h"
 #include "mysqlshdk/libs/ssh/ssh_tunnel_manager.h"
@@ -35,6 +38,38 @@
 namespace mysqlshdk {
 namespace ssh {
 
+Ssh_tunnel::Ssh_tunnel(Ssh_tunnel &&other) { *this = std::move(other); }
+
+Ssh_tunnel &Ssh_tunnel::operator=(Ssh_tunnel &&other) {
+  m_manager = std::exchange(other.m_manager, nullptr);
+  m_port = other.m_port;
+
+  return *this;
+}
+
+void Ssh_tunnel::ref() {
+  if (!m_manager) {
+    return;
+  }
+
+  m_manager->use_tunnel(m_port);
+}
+
+void Ssh_tunnel::unref() {
+  if (!m_manager) {
+    return;
+  }
+
+  try {
+    if (m_manager->release_tunnel(m_port)) {
+      // tunnel has been closed, stop managing it
+      m_manager = nullptr;
+    }
+  } catch (const std::exception &ex) {
+    log_error("An error occurred while closing the tunnel: %s", ex.what());
+  }
+}
+
 Ssh_manager::Ssh_manager() {}
 
 void ssh::Ssh_manager::start() {
@@ -49,18 +84,16 @@ void Ssh_manager::shutdown() {
   m_manager->poke_wakeup_socket();
 }
 
-void Ssh_manager::port_usage_increment(const Ssh_connection_options &config) {
-  if (m_manager) m_manager->use_tunnel(config);
-}
-
-void Ssh_manager::port_usage_decrement(const Ssh_connection_options &config) {
-  if (m_manager) m_manager->release_tunnel(config);
-}
-
-void Ssh_manager::create_tunnel(Ssh_connection_options *ssh_config) {
+Ssh_tunnel Ssh_manager::create_tunnel(Ssh_connection_options *ssh_config) {
   // This function can only be called if there's SSH data
   assert(ssh_config->has_data());
 
+  if (!ssh_config->has_remote_host() || !ssh_config->has_remote_port()) {
+    throw std::invalid_argument(
+        "Host and port for database are required in advance when using SSH "
+        "tunneling functionality");
+  }
+
   // There can be only one Ssh_manager per application and it cannot be started
   // twice. Access to it can be done only through
   // mysqlshdk::ssh::current_ssh_manager();
@@ -68,7 +101,9 @@ void Ssh_manager::create_tunnel(Ssh_connection_options *ssh_config) {
 
   std::call_once(manager_start_once, [this] { start(); });
 
+  std::lock_guard lock{m_create_tunnel_mutex};
   int tunnel_port = m_manager->lookup_tunnel(*ssh_config);
+
   if (tunnel_port > 0) {
     mysqlsh::current_console()->print_info(
         "Existing SSH tunnel found, connecting...");
@@ -103,6 +138,7 @@ void Ssh_manager::create_tunnel(Ssh_connection_options *ssh_config) {
                     static_cast<int>(port));
           ssh_config->set_local_port(port);
           connected = true;
+          tunnel_port = port;
           break;
         }
         case ssh::Ssh_return_type::INVALID_AUTH_DATA: {
@@ -163,6 +199,25 @@ void Ssh_manager::create_tunnel(Ssh_connection_options *ssh_config) {
       }
     }
   }
+
+  return {m_manager.get(), tunnel_port};
+}
+
+Ssh_tunnel Ssh_manager::get_tunnel(const Ssh_connection_options &config) const {
+  int tunnel_port = 0;
+
+  if (m_manager && config.has_local_port()) {
+    if (const auto local_port = config.get_local_port();
+        m_manager->has_tunnel(local_port)) {
+      tunnel_port = local_port;
+    }
+  }
+
+  if (tunnel_port) {
+    return {m_manager.get(), tunnel_port};
+  } else {
+    return {};
+  }
 }
 
 std::vector<Ssh_session_info> Ssh_manager::list_active_tunnels() {
diff --git a/mysqlshdk/libs/ssh/ssh_manager.h b/mysqlshdk/libs/ssh/ssh_manager.h
index 7f740a02dd..d21392d391 100644
--- a/mysqlshdk/libs/ssh/ssh_manager.h
+++ b/mysqlshdk/libs/ssh/ssh_manager.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -28,9 +28,11 @@
 
 #include <map>
 #include <memory>
+#include <mutex>
 #include <string>
 #include <utility>
 #include <vector>
+
 #include "mysqlshdk/libs/ssh/ssh_common.h"
 #include "mysqlshdk/libs/ssh/ssh_connection_options.h"
 
@@ -38,11 +40,49 @@ namespace mysqlshdk {
 namespace ssh {
 class Ssh_tunnel_manager;
 
+/**
+ * Holds a reference to a tunnel, automatically releases it when goes out of
+ * scope.
+ */
+class Ssh_tunnel final {
+ public:
+  Ssh_tunnel() : Ssh_tunnel(nullptr, 0) {}
+
+  Ssh_tunnel(const Ssh_tunnel &) = delete;
+  Ssh_tunnel(Ssh_tunnel &&other);
+
+  Ssh_tunnel &operator=(const Ssh_tunnel &) = delete;
+  Ssh_tunnel &operator=(Ssh_tunnel &&other);
+
+  ~Ssh_tunnel() { unref(); }
+
+  /**
+   * Manually increases the reference count. The `unref()` needs to be called
+   * afterwards, or tunnel will never be deleted.
+   */
+  void ref();
+
+  /**
+   * Manually decreases the reference count.
+   */
+  void unref();
+
+ private:
+  friend class Ssh_manager;
+
+  Ssh_tunnel(Ssh_tunnel_manager *manager, int port)
+      : m_manager(manager), m_port(port) {
+    ref();
+  }
+
+  Ssh_tunnel_manager *m_manager;
+  int m_port;
+};
+
 /**
  * @brief This is the main entrypoint for SSH connections. Ssh_manager takes
  * care of tunnel connection and tracks it's usage so then the tunnel is
  * automatically closed if not needed.
- *
  */
 class Ssh_manager final {
  public:
@@ -53,22 +93,6 @@ class Ssh_manager final {
   Ssh_manager &operator=(const Ssh_manager &other) = delete;
   Ssh_manager &operator=(Ssh_manager &&other) = delete;
 
-  /**
-   * @brief this function must be called when someone want to use a tunnel
-   * port so the manager can track it. By default it's automatically called once
-   * by the create_tunnel.
-   *
-   * @param config Ssh_connection_config SSH connection configuration data
-   */
-  void port_usage_increment(const Ssh_connection_options &config);
-  /**
-   * @brief this function must be called when tunnel port is no longer used, so
-   * the manager can clean it up.
-   *
-   * @param config Ssh_connection_config SSH connection configuration data
-   */
-  void port_usage_decrement(const Ssh_connection_options &config);
-
   /**
    * @brief creates an SSH tunnel connection to the remote machine and open
    * local port to forward data. It will reuse existing tunnel if
@@ -80,7 +104,14 @@ class Ssh_manager final {
    * This function will succeed if tunnel creation succeed or there's existing
    * tunnel that can be used.
    */
-  void create_tunnel(Ssh_connection_options *ssh_config);
+  [[nodiscard]] Ssh_tunnel create_tunnel(Ssh_connection_options *ssh_config);
+
+  /**
+   * Looks up tunnel for the given SSH config, increases its reference count if
+   * found.
+   */
+  [[nodiscard]] Ssh_tunnel get_tunnel(
+      const Ssh_connection_options &config) const;
 
   /**
    * @brief create a list of active SSH tunnel connections
@@ -93,6 +124,7 @@ class Ssh_manager final {
   void start();
   void shutdown();
 
+  std::mutex m_create_tunnel_mutex;
   std::unique_ptr<Ssh_tunnel_manager> m_manager;
 };
 
diff --git a/mysqlshdk/libs/ssh/ssh_tunnel_handler.h b/mysqlshdk/libs/ssh/ssh_tunnel_handler.h
index ecaa63b5ac..d30fd8edad 100644
--- a/mysqlshdk/libs/ssh/ssh_tunnel_handler.h
+++ b/mysqlshdk/libs/ssh/ssh_tunnel_handler.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -68,7 +68,12 @@ class Ssh_tunnel_handler : public Ssh_thread {
   void use() { ++m_usage; }
   int release() {
     assert(m_usage > 0);
-    return --m_usage;
+
+    if (m_usage > 0) {
+      --m_usage;
+    }
+
+    return m_usage;
   }
   Ssh_session_info get_tunnel_info() const {
     return m_session->get_session_info();
diff --git a/mysqlshdk/libs/ssh/ssh_tunnel_manager.cc b/mysqlshdk/libs/ssh/ssh_tunnel_manager.cc
index cd859a23dc..457ad9ccdf 100644
--- a/mysqlshdk/libs/ssh/ssh_tunnel_manager.cc
+++ b/mysqlshdk/libs/ssh/ssh_tunnel_manager.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -84,9 +84,9 @@ Ssh_tunnel_manager::~Ssh_tunnel_manager() {
   }
 }
 
-std::unique_lock<std::recursive_mutex> Ssh_tunnel_manager::lock_socket_list() {
-  std::unique_lock<std::recursive_mutex> mtx(m_socket_mtx);
-  return mtx;
+std::unique_lock<std::recursive_mutex> Ssh_tunnel_manager::lock_socket_list()
+    const {
+  return std::unique_lock{m_socket_mtx};
 }
 
 void Ssh_tunnel_manager::run() { local_socket_handler(); }
@@ -320,28 +320,32 @@ void Ssh_tunnel_manager::poke_wakeup_socket() {
   ssh_close_socket(sock);
 }
 
-void Ssh_tunnel_manager::use_tunnel(const Ssh_connection_options &config) {
-  auto sock_lock = lock_socket_list();
-  for (auto &it : m_socket_list) {
-    if (it.second->config().compare_connection(config)) {
-      it.second->use();
-    }
+void Ssh_tunnel_manager::use_tunnel(int local_port) {
+  const auto sock_lock = lock_socket_list();
+
+  if (const auto sock = find_tunnel(local_port); m_socket_list.end() != sock) {
+    sock->second->use();
   }
 }
 
-void Ssh_tunnel_manager::release_tunnel(const Ssh_connection_options &config) {
-  auto sock_lock = lock_socket_list();
-  for (auto &it : m_socket_list) {
-    if (it.second->config().compare_connection(config)) {
-      auto usage = it.second->release();
+bool Ssh_tunnel_manager::release_tunnel(int local_port) {
+  const auto sock_lock = lock_socket_list();
 
-      if (usage == 0) {
-        disconnect(it.second.get());
-        m_socket_list.erase(it.first);
-        break;
-      }
+  if (const auto sock = find_tunnel(local_port); m_socket_list.end() != sock) {
+    if (sock->second->release() == 0) {
+      disconnect(sock->second.get());
+      m_socket_list.erase(sock->first);
+      return true;
     }
   }
+
+  return false;
+}
+
+bool Ssh_tunnel_manager::has_tunnel(int local_port) const {
+  const auto sock_lock = lock_socket_list();
+
+  return m_socket_list.end() != find_tunnel(local_port);
 }
 
 void Ssh_tunnel_manager::disconnect(
@@ -365,5 +369,13 @@ std::vector<Ssh_session_info> Ssh_tunnel_manager::list_tunnels() {
   return ret_val;
 }
 
+Ssh_tunnel_manager::Sockets::const_iterator Ssh_tunnel_manager::find_tunnel(
+    int local_port) const {
+  return std::find_if(m_socket_list.begin(), m_socket_list.end(),
+                      [local_port](const auto &s) {
+                        return local_port == s.second->local_port();
+                      });
+}
+
 }  // namespace ssh
 }  // namespace mysqlshdk
diff --git a/mysqlshdk/libs/ssh/ssh_tunnel_manager.h b/mysqlshdk/libs/ssh/ssh_tunnel_manager.h
index 4c33522e53..fe2f046db4 100644
--- a/mysqlshdk/libs/ssh/ssh_tunnel_manager.h
+++ b/mysqlshdk/libs/ssh/ssh_tunnel_manager.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -77,8 +77,16 @@ class Ssh_tunnel_manager : public Ssh_thread {
    *
    */
   void poke_wakeup_socket();
-  void use_tunnel(const Ssh_connection_options &config);
-  void release_tunnel(const Ssh_connection_options &config);
+
+  void use_tunnel(int local_port);
+
+  /**
+   * @returns true If tunnel was closed.
+   */
+  bool release_tunnel(int local_port);
+
+  bool has_tunnel(int local_port) const;
+
   std::vector<Ssh_session_info> list_tunnels();
 
  private:
@@ -87,17 +95,21 @@ class Ssh_tunnel_manager : public Ssh_thread {
     int socket_handle;
   };
 
+  using Sockets = std::map<int, std::unique_ptr<Ssh_tunnel_handler>>;
+
   Sock_info create_socket(int backlog = 1);
-  std::unique_lock<std::recursive_mutex> lock_socket_list();
+  std::unique_lock<std::recursive_mutex> lock_socket_list() const;
   void run() override;
   void local_socket_handler();
   std::vector<pollfd> get_socket_list();
   void disconnect(mysqlshdk::ssh::Ssh_tunnel_handler *tunnel_handler);
 
+  Sockets::const_iterator find_tunnel(int local_port) const;
+
   mutable std::recursive_mutex m_socket_mtx;
   uint16_t m_wakeup_socket_port;
   int m_wakeup_socket;
-  std::map<int, std::unique_ptr<Ssh_tunnel_handler>> m_socket_list;
+  Sockets m_socket_list;
 };
 
 }  // namespace ssh
diff --git a/mysqlshdk/libs/storage/compression/gz_file.h b/mysqlshdk/libs/storage/compression/gz_file.h
index 99039dbbd2..3d0d76c68b 100644
--- a/mysqlshdk/libs/storage/compression/gz_file.h
+++ b/mysqlshdk/libs/storage/compression/gz_file.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2020, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -29,6 +29,7 @@
 #include <zlib.h>
 #include <algorithm>
 #include <cassert>
+#include <cstdint>
 #include <limits>
 #include <memory>
 #include <optional>
diff --git a/mysqlshdk/libs/storage/compression/zstd_file.h b/mysqlshdk/libs/storage/compression/zstd_file.h
index 23b44a260b..1b4f94b729 100644
--- a/mysqlshdk/libs/storage/compression/zstd_file.h
+++ b/mysqlshdk/libs/storage/compression/zstd_file.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2020, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -29,6 +29,7 @@
 #include <zstd.h>
 #include <algorithm>
 #include <cassert>
+#include <cstdint>
 #include <limits>
 #include <memory>
 #include <optional>
diff --git a/mysqlshdk/libs/utils/log_sql.cc b/mysqlshdk/libs/utils/log_sql.cc
index a173ef1a1c..dc8a5a50a9 100644
--- a/mysqlshdk/libs/utils/log_sql.cc
+++ b/mysqlshdk/libs/utils/log_sql.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -58,20 +58,6 @@ std::string mask_sql_query(std::string_view sql, bool *out_masked = nullptr) {
       },
       "/*((*/", "/*))*/"));
 }
-
-// We need to know when we are in Dba.* context to be (backward) compatible
-// with --dba-log-sql option.
-// Dba.* context is when we are invoking one of the following objects: Dba.*,
-// ClusterSet.*, Cluster.*, ReplicaSet.*
-bool is_dba_context(std::string_view context) {
-  using namespace std::literals;
-
-  // todo(kg): transform to state machine or use (compile-time) regex
-  return (shcore::str_ibeginswith(context, "dba."sv) ||
-          shcore::str_ibeginswith(context, "cluster."sv) ||
-          shcore::str_ibeginswith(context, "clusterset."sv) ||
-          shcore::str_ibeginswith(context, "replicaset."sv));
-}
 }  // namespace
 
 Log_sql::Log_sql() {
@@ -92,7 +78,6 @@ Log_sql::~Log_sql() = default;
 
 void Log_sql::init(const mysqlsh::Shell_options::Storage &opts) {
   // only called on ctor: doesn't need protection
-  m_dba_log_sql = opts.dba_log_sql;
   m_log_sql_level = get_level_by_name(opts.log_sql);
   m_ignore_patterns = shcore::split_string(opts.log_sql_ignore, ":");
   m_ignore_patterns_all = shcore::split_string(opts.log_sql_ignore_unsafe, ":");
@@ -108,8 +93,6 @@ void Log_sql::handle_notification(const std::string &name,
   auto option = data->get_string("option");
   if (option == SHCORE_LOG_SQL) {
     m_log_sql_level = get_level_by_name(data->get_string("value"));
-  } else if (option == SHCORE_DBA_LOG_SQL) {
-    m_dba_log_sql = data->get_int("value");
   } else if (option == SHCORE_LOG_SQL_IGNORE) {
     std::lock_guard lock(m_mutex);
     auto v = data->get_string("value");
@@ -204,23 +187,6 @@ bool Log_sql::is_off() const { return m_log_sql_level == Log_level::OFF; }
 
 std::pair<bool, std::string> Log_sql::will_log(std::string_view sql,
                                                bool has_error) {
-  // If --dba-log-sql != 0, then it has priority in Dba.* context
-  if (m_num_dba_ctx > 0) {
-    if (m_dba_log_sql >= 2) {
-      return std::make_pair(true, "");
-    }
-
-    if (m_dba_log_sql == 1) {
-      if (shcore::str_ibeginswith(sql, "select")) {
-        return std::make_pair(false, "select");
-      } else if (shcore::str_ibeginswith(sql, "show")) {
-        return std::make_pair(false, "show");
-      } else {
-        return std::make_pair(true, "");
-      }
-    }
-  }
-
   switch (m_log_sql_level) {
     case Log_level::OFF:
       return std::make_pair(false, "");
@@ -249,9 +215,6 @@ void Log_sql::push(std::string_view context) {
   std::lock_guard lock(m_mutex);
 
   m_context_stack.emplace(std::string{context});
-
-  // Track Dba.* context
-  if (is_dba_context(context)) m_num_dba_ctx++;
 }
 
 void Log_sql::pop() {
@@ -260,13 +223,7 @@ void Log_sql::pop() {
   assert(!m_context_stack.empty());
   if (m_context_stack.empty()) return;
 
-  if (is_dba_context(m_context_stack.top())) {
-    assert(m_num_dba_ctx > 0);
-    m_num_dba_ctx--;
-  }
-
   m_context_stack.pop();
-  assert(!m_context_stack.empty() || (m_num_dba_ctx == 0));
 }
 
 Log_sql::Log_level Log_sql::parse_log_level(std::string_view tag) {
diff --git a/mysqlshdk/libs/utils/log_sql.h b/mysqlshdk/libs/utils/log_sql.h
index 4284d45ce0..7429a3099d 100644
--- a/mysqlshdk/libs/utils/log_sql.h
+++ b/mysqlshdk/libs/utils/log_sql.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -88,13 +88,11 @@ class Log_sql : public NotificationObserver {
 
   mutable std::mutex m_mutex;
 
-  std::atomic<int> m_dba_log_sql{0};
   std::atomic<Log_level> m_log_sql_level{Log_level::OFF};
   std::vector<std::string> m_ignore_patterns;
   std::vector<std::string> m_ignore_patterns_all;
   Logger::LOG_LEVEL m_log_level = Logger::LOG_LEVEL::LOG_INFO;
   std::stack<std::string> m_context_stack;
-  size_t m_num_dba_ctx{0};
 };
 
 // implemented in scoped_contexts.cc
diff --git a/mysqlshdk/libs/utils/process_launcher.cc b/mysqlshdk/libs/utils/process_launcher.cc
index 746182c79f..741eecca72 100644
--- a/mysqlshdk/libs/utils/process_launcher.cc
+++ b/mysqlshdk/libs/utils/process_launcher.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2014, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -894,9 +894,14 @@ std::string Process::read_from_terminal() {
       // Since child does not duplicate it to stdin/stdout/stderr, it will
       // remain closed until the child process opens the controlling terminal
       // (i.e. writes the password prompt and waits for user input).
-      if (++retry_count > 1000000) {
+      if (++retry_count > 1'000) {
         report_error("Controlling terminal seems to be closed");
       } else {
+        // sleep before retrying, we wait up to 10 seconds
+        struct timespec ts;
+        ts.tv_sec = 0;
+        ts.tv_nsec = 10'000'000;  // 10ms
+        nanosleep(&ts, nullptr);
         continue;
       }
     } else {
diff --git a/mysqlshdk/libs/utils/string_list_matcher.cc b/mysqlshdk/libs/utils/string_list_matcher.cc
index e5b68aea27..7ea87ce9b5 100644
--- a/mysqlshdk/libs/utils/string_list_matcher.cc
+++ b/mysqlshdk/libs/utils/string_list_matcher.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2023, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -43,14 +43,25 @@ size_t count_spaces(const char *data, size_t size, size_t offset = 0) {
 
 size_t span_comment(const char *data, const char *end, size_t offset) {
   size_t position = offset;
+  std::optional<size_t> comment_end;
+
   if (*(data + offset) == '/' && (data + offset + 1) < end &&
       *(data + offset + 1) == '*') {
-    auto comment_end =
+    comment_end =
         mysqlshdk::utils::span_cstyle_comment(std::string_view(data), offset);
-    if (comment_end == std::string_view::npos) {
+  } else if (*(data + offset) == '-' && (data + offset + 2) < end &&
+             *(data + offset + 1) == '-' &&
+             std::isspace(*(data + offset + 2))) {
+    comment_end = mysqlshdk::utils::span_to_eol(std::string_view(data), offset);
+  } else if (*(data + offset) == '#') {
+    comment_end = mysqlshdk::utils::span_to_eol(std::string_view(data), offset);
+  }
+
+  if (comment_end.has_value()) {
+    if (*comment_end == std::string_view::npos) {
       position = end - data;
     } else {
-      position = comment_end;
+      position = *comment_end;
     }
   }
 
diff --git a/mysqlshdk/libs/utils/utils_general.cc b/mysqlshdk/libs/utils/utils_general.cc
index 7bb0611040..a5999070e8 100644
--- a/mysqlshdk/libs/utils/utils_general.cc
+++ b/mysqlshdk/libs/utils/utils_general.cc
@@ -30,6 +30,7 @@
 #include <utility>
 
 #include "mysqlshdk/libs/textui/textui.h"
+#include "mysqlshdk/libs/utils/utils_string.h"
 
 #include "include/mysh_config.h"
 #include "my_config.h"
@@ -1408,4 +1409,11 @@ bool is_valid_hostname(std::string_view hostname) {
       hostname.begin(), hostname.end(), k_pattern);
 }
 
+bool has_session_track_system_variable(
+    const std::string &session_track_system_variables,
+    const std::string &variable) {
+  return session_track_system_variables == "*" ||
+         shcore::strlist_contains(session_track_system_variables, variable);
+}
+
 }  // namespace shcore
diff --git a/mysqlshdk/libs/utils/utils_general.h b/mysqlshdk/libs/utils/utils_general.h
index 8144f981e7..ad4c406162 100644
--- a/mysqlshdk/libs/utils/utils_general.h
+++ b/mysqlshdk/libs/utils/utils_general.h
@@ -326,6 +326,17 @@ std::string SHCORE_PUBLIC unquote_identifier(const std::string &str,
 
 std::string SHCORE_PUBLIC unquote_sql_string(const std::string &str);
 
+/**
+ * Check whether a variable is enabled in @@session_track_system_variables,
+ * either specifically or because it's set to *
+ *
+ * variable must be in lowercase, as normalized by the server for
+ * @@session_track_system_variables
+ */
+bool has_session_track_system_variable(
+    const std::string &session_track_system_variables,
+    const std::string &variable);
+
 /** Substitute variables in string.
  *
  * str_subvar("hello ${foo}",
diff --git a/mysqlshdk/libs/utils/utils_string.h b/mysqlshdk/libs/utils/utils_string.h
index 3e66cc7411..098f3cb48b 100644
--- a/mysqlshdk/libs/utils/utils_string.h
+++ b/mysqlshdk/libs/utils/utils_string.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2017, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -29,6 +29,7 @@
 #include <wctype.h>
 
 #include <algorithm>
+#include <cassert>
 #include <cctype>
 #include <cstdint>
 #include <cstring>
@@ -757,6 +758,24 @@ inline std::string utf8_lower(std::string_view s) {
   return wide_to_utf8(str_lower(utf8_to_wide(s.data(), s.length())));
 }
 
+/**
+ * Checks whether a , separated list in a string contains the given item.
+ * Spaces not allowed.
+ */
+inline bool strlist_contains(std::string_view list, std::string_view item) {
+  assert(!item.empty());
+
+  if (list.empty()) return false;
+  if (list == item) return true;
+
+  auto pos = list.find(item);
+  if (pos == std::string_view::npos) return false;
+  if (pos > 0 && list[pos - 1] != ',') return false;
+  pos += item.size();
+  if (pos < list.length() && list[pos] != ',') return false;
+  return true;
+}
+
 }  // namespace shcore
 
 #endif  // MYSQLSHDK_LIBS_UTILS_UTILS_STRING_H_
diff --git a/mysqlshdk/libs/yparser/mysql/CMakeLists.txt b/mysqlshdk/libs/yparser/mysql/CMakeLists.txt
index f860eaaeb2..ed55637d40 100644
--- a/mysqlshdk/libs/yparser/mysql/CMakeLists.txt
+++ b/mysqlshdk/libs/yparser/mysql/CMakeLists.txt
@@ -1,4 +1,4 @@
-# Copyright (c) 2024, Oracle and/or its affiliates.
+# Copyright (c) 2024, 2025, Oracle and/or its affiliates.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License, version 2.0,
@@ -251,7 +251,7 @@ function(create_parser_plugin)
         set_target_properties(${PLUGIN_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY_${CONF_UPPER} "${CMAKE_BINARY_DIR}/${CONF}/${PLUGIN_INSTALL_DIR}")
     endforeach()
 
-    if(NOT WIN32 AND NOT APPLE)
+    if(NOT WIN32 AND NOT APPLE AND NOT CMAKE_POSITION_INDEPENDENT_CODE)
         # test executable
         set(TEST_NAME "parser_test_${VERSION_MAJOR_MINOR}")
         # no need to copy the source file, it uses the public interface of the plugin
diff --git a/mysqlshdk/libs/yparser/mysql/yacc/scripts/extract_msvc_symbols.py b/mysqlshdk/libs/yparser/mysql/yacc/scripts/extract_msvc_symbols.py
index 594901634f..352435cc18 100644
--- a/mysqlshdk/libs/yparser/mysql/yacc/scripts/extract_msvc_symbols.py
+++ b/mysqlshdk/libs/yparser/mysql/yacc/scripts/extract_msvc_symbols.py
@@ -1,5 +1,5 @@
 #!/usr/bin/env python3
-# Copyright (c) 2024, Oracle and/or its affiliates.
+# Copyright (c) 2024, 2025, Oracle and/or its affiliates.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License, version 2.0,
@@ -27,7 +27,7 @@
 import subprocess
 
 # external symbol defined in a section
-external_symbol = re.compile("^.+SECT.+External\s+\|\s+(\S+).*$")
+external_symbol = re.compile("^.+SECT.+External\\s+\\|\\s+(\\S+).*$")
 
 def mangled_symbols(lib):
     p = subprocess.Popen(['dumpbin', '/symbols', lib], encoding="ascii", stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
@@ -58,7 +58,7 @@ def mangled_symbols(lib):
     "s_dummy_target@MEM_ROOT",
 ])
 
-symbol_name = re.compile("\?(.+?)@@.*")
+symbol_name = re.compile("\\?(.+?)@@.*")
 
 def use_symbol(mangled):
     m = symbol_name.search(mangled)
diff --git a/mysqlshdk/libs/yparser/mysql/yacc/scripts/strip_yacc.py b/mysqlshdk/libs/yparser/mysql/yacc/scripts/strip_yacc.py
index 077244f59b..78d9f2c783 100755
--- a/mysqlshdk/libs/yparser/mysql/yacc/scripts/strip_yacc.py
+++ b/mysqlshdk/libs/yparser/mysql/yacc/scripts/strip_yacc.py
@@ -1,5 +1,5 @@
 #!/usr/bin/env python3
-# Copyright (c) 2024, Oracle and/or its affiliates.
+# Copyright (c) 2024, 2025, Oracle and/or its affiliates.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License, version 2.0,
@@ -29,8 +29,9 @@
 
 def strip_actions(data, outf, api_prefix, strip_yyundef):
     def peek(data):
+        pos = data.tell()
         c = data.read(1)
-        data.seek(data.tell() - 1)
+        data.seek(pos)
         return c
 
     header = f"""
diff --git a/mysqlshdk/scripting/python_array_wrapper.cc b/mysqlshdk/scripting/python_array_wrapper.cc
index f4e13d5ef3..384afde9d1 100644
--- a/mysqlshdk/scripting/python_array_wrapper.cc
+++ b/mysqlshdk/scripting/python_array_wrapper.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2015, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -347,13 +347,12 @@ PyObject *ao_remove_all(Array_object *self);
 PyObject *ao_reverse(Array_object *self);
 PyObject *ao_sort(Array_object *self, PyObject *args, PyObject *kwds);
 
-#if __GNUC__ > 7 && !defined(__clang__)
-// -Wcast-function-type was added in GCC 8.0, needs to be suppressed here,
-// because we're casting functions with different number of parameters to
-// PyCFunction
+#ifdef __GNUC__
+// -Wcast-function-type needs to be suppressed here, because we're casting
+// functions with different number of parameters to PyCFunction
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wcast-function-type"
-#endif  // __GNUC__ > 7 && !defined(__clang__)
+#endif  // __GNUC__
 
 static PyMethodDef array_object_methods[] = {
     {"__getitem__", (PyCFunction)ao_subscript, METH_O | METH_COEXIST,
@@ -375,9 +374,9 @@ static PyMethodDef array_object_methods[] = {
     {nullptr, nullptr, 0, nullptr},
 };
 
-#if __GNUC__ > 7 && !defined(__clang__)
+#ifdef __GNUC__
 #pragma GCC diagnostic pop
-#endif  // __GNUC__ > 7 && !defined(__clang__)
+#endif  // __GNUC__
 
 ////////////////////////////////////////////////////////////////////////////////
 // Array_object_type
@@ -545,13 +544,12 @@ PyObject *ao_iter_length(Array_object_iterator *self);
 PyObject *ao_iter_reduce(Array_object_iterator *self);
 PyObject *ao_iter_setstate(Array_object_iterator *self, PyObject *state);
 
-#if __GNUC__ > 7 && !defined(__clang__)
-// -Wcast-function-type was added in GCC 8.0, needs to be suppressed here,
-// because we're casting functions with different number of parameters to
-// PyCFunction
+#ifdef __GNUC__
+// -Wcast-function-type needs to be suppressed here, because we're casting
+// functions with different number of parameters to PyCFunction
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wcast-function-type"
-#endif  // __GNUC__ > 7 && !defined(__clang__)
+#endif  // __GNUC__
 
 static PyMethodDef ao_iter_methods[] = {
     {"__length_hint__", (PyCFunction)ao_iter_length, METH_NOARGS,
@@ -561,9 +559,9 @@ static PyMethodDef ao_iter_methods[] = {
     {nullptr, nullptr, 0, nullptr},
 };
 
-#if __GNUC__ > 7 && !defined(__clang__)
+#ifdef __GNUC__
 #pragma GCC diagnostic pop
-#endif  // __GNUC__ > 7 && !defined(__clang__)
+#endif  // __GNUC__
 
 ////////////////////////////////////////////////////////////////////////////////
 // Array_object_iterator_type
@@ -701,13 +699,12 @@ PyObject *ao_rev_iter_length(Array_object_iterator *self);
 PyObject *ao_rev_iter_reduce(Array_object_iterator *self);
 PyObject *ao_rev_iter_setstate(Array_object_iterator *self, PyObject *state);
 
-#if __GNUC__ > 7 && !defined(__clang__)
-// -Wcast-function-type was added in GCC 8.0, needs to be suppressed here,
-// because we're casting functions with different number of parameters to
-// PyCFunction
+#ifdef __GNUC__
+// -Wcast-function-type needs to be suppressed here, because we're casting
+// functions with different number of parameters to PyCFunction
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wcast-function-type"
-#endif  // __GNUC__ > 7 && !defined(__clang__)
+#endif  // __GNUC__
 
 static PyMethodDef ao_riter_methods[] = {
     {"__length_hint__", (PyCFunction)ao_rev_iter_length, METH_NOARGS,
@@ -717,9 +714,9 @@ static PyMethodDef ao_riter_methods[] = {
     {nullptr, nullptr, 0, nullptr},
 };
 
-#if __GNUC__ > 7 && !defined(__clang__)
+#ifdef __GNUC__
 #pragma GCC diagnostic pop
-#endif  // __GNUC__ > 7 && !defined(__clang__)
+#endif  // __GNUC__
 
 ////////////////////////////////////////////////////////////////////////////////
 // Array_object_reverse_iterator_type
diff --git a/mysqlshdk/scripting/python_map_wrapper.cc b/mysqlshdk/scripting/python_map_wrapper.cc
index 830c06786c..11a92ca139 100644
--- a/mysqlshdk/scripting/python_map_wrapper.cc
+++ b/mysqlshdk/scripting/python_map_wrapper.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2015, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -217,13 +217,12 @@ PyObject *set_default(dict::Object *self, PyObject *args);
 PyObject *update(dict::Object *self, PyObject *args, PyObject *kwds);
 PyObject *values(dict::Object *self);
 
-#if __GNUC__ > 7 && !defined(__clang__)
-// -Wcast-function-type was added in GCC 8.0, needs to be suppressed here,
-// because we're casting functions with different number of parameters to
-// PyCFunction
+#ifdef __GNUC__
+// -Wcast-function-type needs to be suppressed here, because we're casting
+// functions with different number of parameters to PyCFunction
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wcast-function-type"
-#endif  // __GNUC__ > 7 && !defined(__clang__)
+#endif  // __GNUC__
 
 PyMethodDef methods[] = {
     {"__contains__", (PyCFunction)has_key, METH_O | METH_COEXIST, contains_doc},
@@ -248,9 +247,9 @@ PyMethodDef methods[] = {
     {nullptr, nullptr, 0, nullptr},
 };
 
-#if __GNUC__ > 7 && !defined(__clang__)
+#ifdef __GNUC__
 #pragma GCC diagnostic pop
-#endif  // __GNUC__ > 7 && !defined(__clang__)
+#endif  // __GNUC__
 
 ////////////////////////////////////////////////////////////////////////////////
 // dict::Type
@@ -468,13 +467,12 @@ PyObject *length(dict::Iterator_base<It> *self);
 template <typename It>
 PyObject *reduce(dict::Iterator_base<It> *self);
 
-#if __GNUC__ > 7 && !defined(__clang__)
-// -Wcast-function-type was added in GCC 8.0, needs to be suppressed here,
-// because we're casting functions with different number of parameters to
-// PyCFunction
+#ifdef __GNUC__
+// -Wcast-function-type needs to be suppressed here, because we're casting
+// functions with different number of parameters to PyCFunction
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wcast-function-type"
-#endif  // __GNUC__ > 7 && !defined(__clang__)
+#endif  // __GNUC__
 
 template <typename It>
 PyMethodDef methods[] = {
@@ -483,9 +481,9 @@ PyMethodDef methods[] = {
     {nullptr, nullptr, 0, nullptr},
 };
 
-#if __GNUC__ > 7 && !defined(__clang__)
+#ifdef __GNUC__
 #pragma GCC diagnostic pop
-#endif  // __GNUC__ > 7 && !defined(__clang__)
+#endif  // __GNUC__
 
 }  // namespace iter
 
@@ -737,13 +735,12 @@ PyDoc_STRVAR(reversed_keys_doc,
 PyObject *iterator(dict::View *self);
 PyObject *reversed(dict::View *self, PyObject *);
 
-#if __GNUC__ > 7 && !defined(__clang__)
-// -Wcast-function-type was added in GCC 8.0, needs to be suppressed here,
-// because we're casting functions with different number of parameters to
-// PyCFunction
+#ifdef __GNUC__
+// -Wcast-function-type needs to be suppressed here, because we're casting
+// functions with different number of parameters to PyCFunction
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wcast-function-type"
-#endif  // __GNUC__ > 7 && !defined(__clang__)
+#endif  // __GNUC__
 
 PyMethodDef methods[] = {
     {"isdisjoint", (PyCFunction)isdisjoint, METH_O, isdisjoint_doc},
@@ -751,9 +748,9 @@ PyMethodDef methods[] = {
     {nullptr, nullptr, 0, nullptr},
 };
 
-#if __GNUC__ > 7 && !defined(__clang__)
+#ifdef __GNUC__
 #pragma GCC diagnostic pop
-#endif  // __GNUC__ > 7 && !defined(__clang__)
+#endif  // __GNUC__
 
 }  // namespace view::keys
 
@@ -935,22 +932,21 @@ PyDoc_STRVAR(reversed_values_doc,
 PyObject *iterator(dict::View *self);
 PyObject *reversed(dict::View *self, PyObject *);
 
-#if __GNUC__ > 7 && !defined(__clang__)
-// -Wcast-function-type was added in GCC 8.0, needs to be suppressed here,
-// because we're casting functions with different number of parameters to
-// PyCFunction
+#ifdef __GNUC__
+// -Wcast-function-type needs to be suppressed here, because we're casting
+// functions with different number of parameters to PyCFunction
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wcast-function-type"
-#endif  // __GNUC__ > 7 && !defined(__clang__)
+#endif  // __GNUC__
 
 PyMethodDef methods[] = {
     {"__reversed__", (PyCFunction)reversed, METH_NOARGS, reversed_values_doc},
     {nullptr, nullptr, 0, nullptr},
 };
 
-#if __GNUC__ > 7 && !defined(__clang__)
+#ifdef __GNUC__
 #pragma GCC diagnostic pop
-#endif  // __GNUC__ > 7 && !defined(__clang__)
+#endif  // __GNUC__
 
 }  // namespace view::values
 
@@ -1134,13 +1130,12 @@ PyDoc_STRVAR(reversed_items_doc,
 PyObject *iterator(dict::View *self);
 PyObject *reversed(dict::View *self, PyObject *);
 
-#if __GNUC__ > 7 && !defined(__clang__)
-// -Wcast-function-type was added in GCC 8.0, needs to be suppressed here,
-// because we're casting functions with different number of parameters to
-// PyCFunction
+#ifdef __GNUC__
+// -Wcast-function-type needs to be suppressed here, because we're casting
+// functions with different number of parameters to PyCFunction
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wcast-function-type"
-#endif  // __GNUC__ > 7 && !defined(__clang__)
+#endif  // __GNUC__
 
 PyMethodDef methods[] = {
     {"isdisjoint", (PyCFunction)isdisjoint, METH_O, isdisjoint_doc},
@@ -1148,9 +1143,9 @@ PyMethodDef methods[] = {
     {nullptr, nullptr, 0, nullptr},
 };
 
-#if __GNUC__ > 7 && !defined(__clang__)
+#ifdef __GNUC__
 #pragma GCC diagnostic pop
-#endif  // __GNUC__ > 7 && !defined(__clang__)
+#endif  // __GNUC__
 
 }  // namespace view::items
 
diff --git a/mysqlshdk/shellcore/base_session.cc b/mysqlshdk/shellcore/base_session.cc
index 38c10c5380..29dcf399ee 100644
--- a/mysqlshdk/shellcore/base_session.cc
+++ b/mysqlshdk/shellcore/base_session.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2015, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -251,6 +251,9 @@ std::shared_ptr<mysqlshdk::db::IResult> ShellBaseSession::execute_sql(
     bool use_sql_handlers) {
   std::shared_ptr<mysqlshdk::db::IResult> ret_val;
 
+  const auto full_query = sub_query_placeholders(query, args);
+  query = full_query;
+
   // Gets the SQL handlers to be executed for the given query
   shcore::Sql_handler_ptr sql_handler = nullptr;
 
@@ -261,10 +264,6 @@ std::shared_ptr<mysqlshdk::db::IResult> ShellBaseSession::execute_sql(
         shcore::current_sql_handler_registry()->get_handler_for_sql(query);
   }
 
-  shcore::Argument_list sql_handler_args;
-  sql_handler_args.push_back(shcore::Value(get_shared_this()));
-  sql_handler_args.push_back(shcore::Value(query));
-
   if (sql_handler) {
     if (m_active_custom_sql.empty()) {
       m_active_custom_sql = query;
@@ -280,7 +279,11 @@ std::shared_ptr<mysqlshdk::db::IResult> ShellBaseSession::execute_sql(
     shcore::Scoped_callback enable_sql_handlers(
         [this]() { m_active_custom_sql.clear(); });
 
-    auto value = sql_handler->callback()->invoke(sql_handler_args);
+    shcore::Argument_list sql_handler_args;
+    sql_handler_args.emplace_back(get_shared_this());
+    sql_handler_args.emplace_back(query);
+
+    const auto value = sql_handler->callback()->invoke(sql_handler_args);
 
     // An SQL handler may return either nothing or a Result object
     if (value && !value.is_null()) {
@@ -308,60 +311,12 @@ std::shared_ptr<mysqlshdk::db::IResult> ShellBaseSession::execute_sql(
   if (!ret_val) {
     // The statement will be executed on this session ONLY if it was not already
     // executed by a handler
-    ret_val = do_execute_sql(query, args, query_attributes);
+    ret_val = do_execute_sql(query, {}, query_attributes);
   }
 
   return ret_val;
 }
 
-void ShellBaseSession::enable_sql_mode_tracking() {
-  if (m_is_sql_mode_tracking_enabled) return;
-
-  auto session = get_core_session();
-
-  if (session->get_connection_options().get_session_type() !=
-      mysqlsh::SessionType::Classic)
-    return;
-  if (session->get_server_version() < mysqlshdk::utils::Version(5, 7, 0))
-    return;
-
-  std::string current_value;
-  try {
-    const auto res =
-        session->query("SELECT @@SESSION.session_track_system_variables");
-    const auto row = res->fetch_one();
-    if (!row)
-      throw std::logic_error(
-          "Unable to determine current session tracked system variables.");
-
-    current_value = shcore::str_lower(row->get_string(0));
-
-  } catch (const mysqlshdk::db::Error &) {
-    return;
-  }
-
-  m_is_sql_mode_tracking_enabled =
-      current_value.find("sql_mode") != std::string::npos ||
-      current_value == "*";
-
-  if (m_is_sql_mode_tracking_enabled) {
-    log_info("Already tracking 'sql_mode'system variable.");
-    return;
-  }
-
-  current_value.append(current_value.empty() ? "sql_mode" : ", sql_mode");
-
-  try {
-    session->executef("SET SESSION session_track_system_variables = ?;",
-                      current_value);
-  } catch (const mysqlshdk::db::Error &) {
-    return;
-  }
-
-  m_is_sql_mode_tracking_enabled = true;
-  log_info("Now tracking 'sql_mode' system variable.");
-}
-
 std::string ShellBaseSession::get_quoted_name(std::string_view name) {
   std::string quoted_name;
   quoted_name.reserve(name.size() + 2);
@@ -399,23 +354,19 @@ std::string ShellBaseSession::get_default_schema() {
 }
 
 void ShellBaseSession::reconnect() {
-  const auto &ssh_data = _connection_options.get_ssh_options();
+  // we need to hold the reference to the SSH tunnel while we reconnect, to
+  // prevent it from being closed when the session closes
+  const auto ssh = mysqlshdk::ssh::current_ssh_manager()->get_tunnel(
+      _connection_options.get_ssh_options());
 
-  // we need to increment port usage and decrement it later cause during
-  // reconnect, the decrement will be called, so tunnel would be lost
-  mysqlshdk::ssh::current_ssh_manager()->port_usage_increment(ssh_data);
-
-  shcore::Scoped_callback scoped([ssh_data] {
-    mysqlshdk::ssh::current_ssh_manager()->port_usage_decrement(ssh_data);
-  });
   connect(_connection_options);
 }
 
 std::string ShellBaseSession::sub_query_placeholders(
-    const std::string &query, const shcore::Array_t &args) {
-  if (!args) return query;
+    std::string_view query, const shcore::Array_t &args) {
+  if (!args) return std::string{query};
 
-  shcore::sqlstring squery(query.c_str(), 0);
+  shcore::sqlstring squery(std::string{query}, 0);
   int i = 0;
   for (const shcore::Value &value : *args) {
     try {
@@ -457,7 +408,7 @@ std::string ShellBaseSession::sub_query_placeholders(
         "Insufficient number of values for placeholders in query");
   }
 
-  return query;
+  throw std::logic_error{"ShellBaseSession::sub_query_placeholders()"};
 }
 
 void ShellBaseSession::set_query_attributes(const shcore::Dictionary_t &args) {
@@ -465,3 +416,32 @@ void ShellBaseSession::set_query_attributes(const shcore::Dictionary_t &args) {
     m_query_attributes.handle_errors(true);
   }
 }
+
+void ShellBaseSession::set_client_data(const std::string &key,
+                                       const shcore::Value &value) {
+  if (!m_client_data) m_client_data = shcore::make_dict();
+
+  (*m_client_data)[key] = value;
+}
+
+shcore::Value ShellBaseSession::get_client_data(const std::string &key) const {
+  if (m_client_data) {
+    if (auto it = m_client_data->find(key); it != m_client_data->end()) {
+      return it->second;
+    }
+  }
+  return {};
+}
+
+std::string ShellBaseSession::get_sql_mode() {
+  if (!m_tracking_sql_mode) get_core_session()->refresh_sql_mode();
+  return get_core_session()->get_sql_mode();
+}
+
+std::string ShellBaseSession::track_system_variable(
+    const std::string &variable) {
+  auto new_value = get_core_session()->track_system_variable(variable);
+  m_tracking_sql_mode =
+      shcore::has_session_track_system_variable(new_value, "sql_mode");
+  return new_value;
+}
diff --git a/mysqlshdk/shellcore/credential_manager.cc b/mysqlshdk/shellcore/credential_manager.cc
index 222878a9c2..682cea01bf 100644
--- a/mysqlshdk/shellcore/credential_manager.cc
+++ b/mysqlshdk/shellcore/credential_manager.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -26,9 +26,15 @@
 #include "mysqlshdk/shellcore/credential_manager.h"
 
 #include <algorithm>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
 
 #include "mysql-secret-store/include/mysql-secret-store/api.h"
 #include "mysqlshdk/include/shellcore/scoped_contexts.h"
+#include "mysqlshdk/libs/utils/utils_encoding.h"
 #include "mysqlshdk/libs/utils/utils_general.h"
 #include "mysqlshdk/libs/utils/utils_string.h"
 
@@ -66,6 +72,8 @@ constexpr auto k_save_passwords_prompt = "prompt";
 
 constexpr auto k_no_such_secret_error = "Could not find the secret";
 constexpr auto k_invalid_url_error = "Invalid URL";
+constexpr auto k_invalid_secret_error =
+    "Secret contains a disallowed character";
 
 Helper_name get_helper_by_name(const std::string &name) {
   auto helpers = get_available_helpers();
@@ -126,7 +134,7 @@ Credential_manager::Save_passwords to_save_passwords(const std::string &str) {
     return Credential_manager::Save_passwords::PROMPT;
   } else {
     throw Exception::value_error(
-        "The option " + std::string{k_save_passwords_option} +
+        "The option " + std::string(k_save_passwords_option) +
         " must be one of: " +
         str_join(std::vector<std::string>{k_save_passwords_always,
                                           k_save_passwords_prompt,
@@ -151,6 +159,18 @@ std::string to_string(Credential_manager::Save_passwords val) {
   throw std::runtime_error{"Unknown Save_passwords value"};
 }
 
+const char *to_message(Secret_type type) {
+  switch (type) {
+    case Secret_type::PASSWORD:
+      return "credential";
+
+    case Secret_type::GENERIC:
+      return "secret";
+  }
+
+  throw std::runtime_error{"Unknown Secret_type value"};
+}
+
 }  // namespace
 
 Credential_manager::Credential_manager() {
@@ -165,7 +185,7 @@ Credential_manager &Credential_manager::get() {
 void Credential_manager::initialize() {
   if (!m_is_initialized) {
     set_logger([](std::string_view msg) {
-      log_debug2("%.*s", (int)msg.size(), msg.data());
+      log_debug2("%.*s", static_cast<int>(msg.size()), msg.data());
     });
 
     if (k_disabled_helper_name == m_helper_string) {
@@ -248,7 +268,7 @@ void Credential_manager::register_options(Options *options) {
           return filters;
         } catch (const std::exception &ex) {
           throw Exception::value_error("The option " +
-                                       std::string{k_exclude_filters_option} +
+                                       std::string(k_exclude_filters_option) +
                                        " must be an array of strings");
         }
       },
@@ -303,7 +323,7 @@ bool Credential_manager::get_password(mysqlshdk::IConnection *options) const {
     std::string password;
     auto secret_spec = get_secret_spec(*options);
     log_debug2("Retrieving password from credential manager for '%s'",
-               secret_spec.url.c_str());
+               secret_spec.id.c_str());
     bool ret = m_helper->get(secret_spec, &password);
 
     if (ret) {
@@ -356,10 +376,7 @@ bool Credential_manager::remove_password(
 
 bool Credential_manager::get_credential(const std::string &url,
                                         std::string *credential) const {
-  if (!m_helper) {
-    throw Exception::runtime_error(
-        "Cannot get the credential, current credential helper is invalid");
-  }
+  check_helper("get the credential");
 
   bool ret = m_helper->get({Secret_type::PASSWORD, url}, credential);
   if (!ret) {
@@ -372,88 +389,156 @@ bool Credential_manager::get_credential(const std::string &url,
   return ret;
 }
 
-void Credential_manager::store_credential(const std::string &url,
-                                          const std::string &credential) {
-  if (!m_helper) {
-    throw Exception::runtime_error(
-        "Cannot save the credential, current credential helper is invalid");
+void Credential_manager::store_secret(const std::string &id,
+                                      const std::string &secret) {
+  // we prepend 'r' for a unmodified secret and 'b' for a base64-encoded secret
+  // we encode the secret if helper reports an error when storing the raw secret
+  try {
+    store_secret(Secret_type::GENERIC, id, "r" + secret);
+  } catch (const std::exception &e) {
+    if (std::string::npos ==
+        std::string_view{e.what()}.find(k_invalid_secret_error)) {
+      throw;
+    }
+
+    std::string base64;
+
+    if (!shcore::encode_base64(secret, &base64)) {
+      // in case of encoding errors, just throw the original exception
+      throw;
+    }
+
+    store_secret(Secret_type::GENERIC, id, "b" + base64);
   }
+}
 
-  if (!m_helper->store({Secret_type::PASSWORD, url}, credential)) {
-    auto error = m_helper->get_last_error();
+void Credential_manager::store_secret(Secret_type type, const std::string &id,
+                                      const std::string &secret) {
+  check_helper(str_format("save the %s", to_message(type)));
+
+  if (!m_helper->store({type, id}, secret)) {
+    const auto error = m_helper->get_last_error();
 
     if (std::string::npos != error.find(k_invalid_url_error)) {
       throw Exception::argument_error(error);
     } else {
-      throw Exception::runtime_error("Failed to save the credential: " + error);
+      throw Exception::runtime_error(str_format(
+          "Failed to save the %s: %s", to_message(type), error.c_str()));
     }
   }
 }
 
-void Credential_manager::delete_credential(const std::string &url) {
-  if (!m_helper) {
-    throw Exception::runtime_error(
-        "Cannot delete the credential, current credential helper is invalid");
+std::string Credential_manager::read_secret(const std::string &id) {
+  auto secret = read_secret(Secret_type::GENERIC, id);
+
+  if (secret.empty()) {
+    return secret;
   }
 
-  if (!m_helper->erase({Secret_type::PASSWORD, url})) {
-    auto error = m_helper->get_last_error();
+  switch (secret.front()) {
+    case 'r':
+      return secret.substr(1);
+
+    case 'b': {
+      std::string decoded;
+
+      if (!shcore::decode_base64(secret.substr(1), &decoded)) {
+        // if decoding fails, just return the whole secret
+        return secret;
+      }
+
+      return decoded;
+    }
+
+    default:
+      return secret;
+  }
+}
+
+std::string Credential_manager::read_secret(Secret_type type,
+                                            const std::string &id) {
+  check_helper(str_format("read the %s", to_message(type)));
+
+  std::string secret;
+
+  if (!m_helper->get({type, id}, &secret)) {
+    const auto error = m_helper->get_last_error();
 
     if (std::string::npos != error.find(k_invalid_url_error)) {
       throw Exception::argument_error(error);
     } else {
-      throw Exception::runtime_error("Failed to delete the credential: " +
-                                     error);
+      throw Exception::runtime_error(str_format(
+          "Failed to read the %s: %s", to_message(type), error.c_str()));
     }
   }
+
+  return secret;
 }
 
-void Credential_manager::delete_all_credentials() {
-  if (!m_helper) {
-    throw Exception::runtime_error(
-        "Cannot delete all credentials, current credential helper is invalid");
+void Credential_manager::delete_secret(Secret_type type,
+                                       const std::string &id) {
+  check_helper(str_format("delete the %s", to_message(type)));
+
+  if (!m_helper->erase({type, id})) {
+    const auto error = m_helper->get_last_error();
+
+    if (std::string::npos != error.find(k_invalid_url_error)) {
+      throw Exception::argument_error(error);
+    } else {
+      throw Exception::runtime_error(str_format(
+          "Failed to delete the %s: %s", to_message(type), error.c_str()));
+    }
   }
+}
+
+void Credential_manager::delete_all_secrets(Secret_type type) {
+  check_helper(str_format("delete all %ss", to_message(type)));
 
   std::vector<Secret_spec> specs;
 
-  if (!m_helper->list(&specs)) {
+  if (!m_helper->list(&specs, type)) {
     throw Exception::runtime_error(
-        "Failed to obtain list of credentials to delete: " +
-        m_helper->get_last_error());
+        str_format("Failed to obtain list of %ss to delete: %s",
+                   to_message(type), m_helper->get_last_error().c_str()));
   }
 
   std::vector<std::string> errors;
 
   for (const auto &s : specs) {
-    if (!m_helper->erase(s)) {
-      errors.emplace_back("Failed to delete '" + s.url +
-                          "': " + m_helper->get_last_error());
+    if (type == s.type) {
+      if (!m_helper->erase(s)) {
+        errors.emplace_back("Failed to delete '" + s.id +
+                            "': " + m_helper->get_last_error());
+      }
     }
   }
 
   if (!errors.empty()) {
-    throw Exception::runtime_error("Failed to delete all credentials:\n" +
-                                   shcore::str_join(errors, "\n"));
+    throw Exception::runtime_error(
+        str_format("Failed to delete all %ss:\n%s", to_message(type),
+                   shcore::str_join(errors, "\n").c_str()));
   }
 }
 
-std::vector<std::string> Credential_manager::list_credentials() const {
-  if (!m_helper) {
-    throw Exception::runtime_error(
-        "Cannot list credentials, current credential helper is invalid");
-  }
+std::vector<std::string> Credential_manager::list_secrets(
+    Secret_type type) const {
+  check_helper(str_format("list %ss", to_message(type)));
 
-  std::vector<std::string> ret;
   std::vector<Secret_spec> specs;
 
-  if (!m_helper->list(&specs)) {
-    throw Exception::runtime_error("Failed to list credentials: " +
-                                   m_helper->get_last_error());
+  if (!m_helper->list(&specs, type)) {
+    throw Exception::runtime_error(
+        str_format("Failed to list %ss: %s", to_message(type),
+                   m_helper->get_last_error().c_str()));
   }
 
-  for (const auto &s : specs) {
-    if (s.type == Secret_type::PASSWORD) {
-      ret.emplace_back(s.url);
+  std::vector<std::string> ret;
+
+  for (auto &s : specs) {
+    // type was added to the list() command by WL#16958, helpers may not support
+    // it, double check the type
+    if (type == s.type) {
+      ret.emplace_back(std::move(s.id));
     }
   }
 
@@ -513,4 +598,11 @@ bool Credential_manager::is_ignored_url(const std::string &url) const {
   return false;
 }
 
+void Credential_manager::check_helper(const std::string &context) const {
+  if (!m_helper) {
+    throw Exception::runtime_error(str_format(
+        "Cannot %s, current credential helper is invalid", context.c_str()));
+  }
+}
+
 }  // namespace shcore
diff --git a/mysqlshdk/shellcore/credential_manager.h b/mysqlshdk/shellcore/credential_manager.h
index 5973c59779..5b05451911 100644
--- a/mysqlshdk/shellcore/credential_manager.h
+++ b/mysqlshdk/shellcore/credential_manager.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -30,6 +30,7 @@
 #include <string>
 #include <vector>
 
+#include "mysql-secret-store/include/mysql-secret-store/secret_type.h"
 #include "mysqlshdk/include/shellcore/shell_notifications.h"
 #include "mysqlshdk/libs/db/connection_options.h"
 #include "mysqlshdk/libs/utils/connection.h"
@@ -82,22 +83,60 @@ class Credential_manager : public NotificationObserver {
 
   std::vector<std::string> list_credential_helpers() const;
 
-  void store_credential(const std::string &url, const std::string &credential);
+  inline void store_credential(const std::string &url,
+                               const std::string &credential) {
+    store_secret(Secret_type::PASSWORD, url, credential);
+  }
 
-  void delete_credential(const std::string &url);
+  inline void delete_credential(const std::string &url) {
+    delete_secret(Secret_type::PASSWORD, url);
+  }
 
-  void delete_all_credentials();
+  inline void delete_all_credentials() {
+    delete_all_secrets(Secret_type::PASSWORD);
+  }
 
-  std::vector<std::string> list_credentials() const;
+  inline std::vector<std::string> list_credentials() const {
+    return list_secrets(Secret_type::PASSWORD);
+  }
+
+  void store_secret(const std::string &id, const std::string &secret);
+
+  std::string read_secret(const std::string &id);
+
+  inline void delete_secret(const std::string &id) {
+    delete_secret(Secret_type::GENERIC, id);
+  }
+
+  inline void delete_all_secrets() { delete_all_secrets(Secret_type::GENERIC); }
+
+  inline std::vector<std::string> list_secrets() const {
+    return list_secrets(Secret_type::GENERIC);
+  }
 
  private:
+  using Secret_type = ::mysql::secret_store::api::Secret_type;
+
   Credential_manager();
   ~Credential_manager() = default;
 
+  void check_helper(const std::string &context) const;
+
   void add_ignore_filter(const std::string &filter);
 
   bool is_ignored_url(const std::string &url) const;
 
+  void store_secret(Secret_type type, const std::string &id,
+                    const std::string &secret);
+
+  std::string read_secret(Secret_type type, const std::string &id);
+
+  void delete_secret(Secret_type type, const std::string &id);
+
+  void delete_all_secrets(Secret_type type);
+
+  std::vector<std::string> list_secrets(Secret_type type) const;
+
   std::unique_ptr<::mysql::secret_store::api::Helper_interface> m_helper;
   std::string m_helper_string;
   Save_passwords m_save_passwords = Save_passwords::PROMPT;
diff --git a/mysqlshdk/shellcore/shell_options.cc b/mysqlshdk/shellcore/shell_options.cc
index 96d1abc671..ba7589df91 100644
--- a/mysqlshdk/shellcore/shell_options.cc
+++ b/mysqlshdk/shellcore/shell_options.cc
@@ -85,6 +85,8 @@ mysqlshdk::db::Connection_options Shell_options::Storage::connection_options()
 
   if (!ssh.uri.empty()) {
     auto ssh_dict = shcore::make_dict();
+    (*ssh_dict)[mysqlshdk::db::kSsh] = shcore::Value(ssh.uri);
+
     if (!ssh.identity_file.empty()) {
       (*ssh_dict)[mysqlshdk::db::kSshIdentityFile] =
           shcore::Value(ssh.identity_file);
@@ -95,13 +97,11 @@ mysqlshdk::db::Connection_options Shell_options::Storage::connection_options()
           shcore::Value(ssh.config_file);
     }
 
-    (*ssh_dict)[mysqlshdk::db::kSsh] = shcore::Value(ssh.uri);
     if (!ssh.pwd.empty()) {
       (*ssh_dict)[mysqlshdk::db::kSshPassword] = shcore::Value(ssh.pwd);
     }
 
-    auto ssh_options = mysqlsh::get_ssh_options(ssh_dict);
-    target_server.set_ssh_options(ssh_options);
+    target_server.set_ssh_options(mysqlsh::get_ssh_options(ssh_dict));
   }
 
   if (has_multi_passwords()) {
@@ -123,14 +123,11 @@ mysqlsh::SessionType session_type_for_option(const std::string &option) {
     return mysqlsh::SessionType::Classic;
   } else if (shcore::str_beginswith(option, "mysqlx://")) {
     return mysqlsh::SessionType::X;
-  } else if (option == "-mc" || option == "--mc" || option == "--mysql" ||
-             option == "--sqlc") {
+  } else if (option == "--mc" || option == "--mysql" || option == "--sqlc") {
     return mysqlsh::SessionType::Classic;
-  } else if (option == "-mx" || option == "--mx" || option == "--mysqlx" ||
-             option == "--sqlx") {
+  } else if (option == "--mx" || option == "--mysqlx" || option == "--sqlx") {
     return mysqlsh::SessionType::X;
   } else {
-    // -ma, --ma
     return mysqlsh::SessionType::Auto;
   }
 }
@@ -455,6 +452,15 @@ Shell_options::Shell_options(
             throw std::invalid_argument(
                 "The value of 'compression-level' must be an integer.");
           }
+        })
+    (cmdline("--local-infile"),
+        "Enable LOAD DATA LOCAL INFILE. (classic protocol only)",
+        [this](const std::string&, const char*) {
+          if (storage.connection_data.has(mysqlshdk::db::kLocalInfile)) {
+            storage.connection_data.remove(mysqlshdk::db::kLocalInfile);
+          }
+
+          storage.connection_data.set(mysqlshdk::db::kLocalInfile, "true");
         });
 
   add_startup_options(!flags.is_set(Option_flags::CONNECTION_ONLY))
@@ -566,18 +572,6 @@ Shell_options::Shell_options(
         create_result_format_handler("vertical"));
 
   add_named_options(!flags.is_set(Option_flags::CONNECTION_ONLY))
-    (&storage.result_format, "table", "outputFormat",
-        "outputFormat option was deprecated, "
-        "please use " SHCORE_RESULT_FORMAT " to set result format and --json "
-        "command line option to wrap output in JSON instead.",
-        [this](const std::string &val, Source s) {
-          m_on_warning("WARNING: outputFormat option was deprecated, "
-              "please use " SHCORE_RESULT_FORMAT " to set result format and"
-              " --json command line option to wrap output in JSON instead.");
-          get_option(SHCORE_RESULT_FORMAT).set(val, s);
-          storage.wrap_json = shcore::str_beginswith(val, "json") ? val : "off";
-          return storage.result_format;
-        })
     (&storage.result_format, "table", SHCORE_RESULT_FORMAT,
         cmdline("--result-format=<value>"),
         "Determines format of results. Allowed values:"
@@ -683,12 +677,6 @@ Shell_options::Shell_options(
 
           return level;
         })
-    (&storage.dba_log_sql, 0, SHCORE_DBA_LOG_SQL,
-        cmdline("--dba-log-sql[={0|1|2}]"),
-        "Log SQL statements executed by AdminAPI operations: "
-        "0 - logging disabled; 1 - log statements other than SELECT and SHOW; "
-        "2 - log all statements. Option takes precedence over --log-sql in "
-        "Dba.* context if enabled.", shcore::opts::Range<int>(0, 2))
     (&storage.log_sql, "error", SHCORE_LOG_SQL,
         cmdline("--log-sql=off|error|on|all|unfiltered"),
         "Log SQL statements: "
@@ -882,9 +870,6 @@ Shell_options::Shell_options(
     assign_value(&storage.disable_user_plugins, true));
 
   add_startup_options(!flags.is_set(Option_flags::CONNECTION_ONLY))
-    (&storage.execute_dba_statement, "",
-        cmdline("--dba=enableXProtocol"), "Enable the X protocol in the target "
-        "server. Requires a connection using classic session. Deprecated.")
 #ifndef NDEBUG
     (cmdline("--trace-proto"), assign_value(&storage.trace_protocol, true))
 #endif
@@ -1229,52 +1214,6 @@ bool Shell_options::custom_cmdline_handler(Iterator *iterator) {
       storage.connection_data.clear_mfa_password(mfa_password);
       iterator->next_no_value();
     }
-  } else if ("-m" == option) {
-    bool handled = false;
-
-    if (iterator->value()) {
-      const std::string value = option + iterator->value();
-      const char *replacement = nullptr;
-
-      if ("-ma" == value) {
-        handled = true;
-      } else if ("-mc" == value) {
-        replacement = "--mc";
-      } else if ("-mx" == value) {
-        replacement = "--mx";
-      }
-
-      if (nullptr != replacement) {
-        handled = true;
-      }
-
-      if (handled) {
-        deprecated(m_on_warning, replacement,
-                   std::bind(&Shell_options::override_session_type, this, _1,
-                             _2))(value, nullptr);
-        iterator->next();
-      }
-    }
-
-    if (!handled) {
-      throw std::invalid_argument(iterator->iterator()->first() +
-                                  std::string(": unknown option -m"));
-    }
-  } else if ("--dba-log-sql" == option) {
-    m_on_warning(
-        "WARNING: The --dba-log-sql option was deprecated, "
-        "please use --log-sql instead.");
-    return false;
-  } else if ("--dba" == option) {
-    if (iterator->value()) {
-      const std::string value = iterator->value();
-      if ("enableXProtocol" == value) {
-        deprecated(m_on_warning, nullptr,
-                   std::bind(&Shell_options::override_session_type, this, _1,
-                             _2))(value, nullptr);
-      }
-    }
-    return false;
   } else {
     return false;
   }
diff --git a/mysqlshdk/shellcore/shell_sql.cc b/mysqlshdk/shellcore/shell_sql.cc
index 44e56d8ea4..2e99a0926b 100644
--- a/mysqlshdk/shellcore/shell_sql.cc
+++ b/mysqlshdk/shellcore/shell_sql.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2014, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -138,7 +138,6 @@ bool Shell_sql::process_sql(std::string_view query, std::string_view delimiter,
                             std::shared_ptr<mysqlsh::ShellBaseSession> session,
                             mysqlshdk::utils::Sql_splitter *splitter) {
   bool ret_val = false;
-  std::optional<bool> server_state_changed;
   if (session) {
     try {
       std::shared_ptr<mysqlshdk::db::IResult> result;
@@ -161,14 +160,6 @@ bool Shell_sql::process_sql(std::string_view query, std::string_view delimiter,
                                         true);
 
           session->query_attribute_store().clear();
-
-          if ((mysqlshdk::db::replay::g_replay_mode ==
-               mysqlshdk::db::replay::Mode::Direct) &&
-              session->is_sql_mode_tracking_enabled()) {
-            server_state_changed =
-                session->get_core_session()->get_server_status() &
-                SERVER_SESSION_STATE_CHANGED;
-          }
         }
 
         timer.stage_end();
@@ -211,43 +202,41 @@ bool Shell_sql::process_sql(std::string_view query, std::string_view delimiter,
 
   _last_handled.append(query).append(delimiter);
 
-  // check if the value of sql_mode have changed - statement can either begin
-  // with 'SET' or, because our splitter does not strip them, with a C style
-  // comment e.g.: /*...*/ set sql_mode...
-  if (ret_val) {
-    auto core_session = session->get_core_session();
-    if (server_state_changed) {
-      if (*server_state_changed) {
-        core_session->refresh_sql_mode();
-      }
-
+  if (auto core_session = session->get_core_session()) {
+    if (session->is_sql_mode_tracking_enabled()) {
       splitter->set_ansi_quotes(core_session->ansi_quotes_enabled());
       splitter->set_no_backslash_escapes(
           core_session->no_backslash_escapes_enabled());
-
-    } else if (query.size() > 12 &&
-               (query[2] == 't' || query[2] == 'T' || query[1] == '*')) {
-      mysqlshdk::utils::SQL_iterator it(_last_handled);
-      auto next = it.next_token();
-      if (shcore::str_caseeq(next, "SET")) {
-        constexpr std::array<std::string_view, 4> mods = {"GLOBAL", "PERSIST",
-                                                          "SESSION", "LOCAL"};
-        next = it.next_token();
-        for (auto mod : mods) {
-          if (shcore::str_caseeq(next, mod)) {
+    } else {
+      // check if the value of sql_mode have changed - statement can either
+      // begin with 'SET' or, because our splitter does not strip them, with a C
+      // style comment e.g.: /*...*/ set sql_mode...
+      if (ret_val) {
+        if (query.size() > 12 &&
+            (query[2] == 't' || query[2] == 'T' || query[1] == '*')) {
+          mysqlshdk::utils::SQL_iterator it(_last_handled);
+          auto next = it.next_token();
+          if (shcore::str_caseeq(next, "SET")) {
+            constexpr std::array<std::string_view, 4> mods = {
+                "GLOBAL", "PERSIST", "SESSION", "LOCAL"};
             next = it.next_token();
-            break;
+            for (auto mod : mods) {
+              if (shcore::str_caseeq(next, mod)) {
+                next = it.next_token();
+                break;
+              }
+            }
+
+            while (next == "@") next = it.next_token();
+
+            if (shcore::str_upper(next).find("SQL_MODE") != std::string::npos) {
+              core_session->refresh_sql_mode();
+              splitter->set_ansi_quotes(core_session->ansi_quotes_enabled());
+              splitter->set_no_backslash_escapes(
+                  core_session->no_backslash_escapes_enabled());
+            }
           }
         }
-
-        while (next == "@") next = it.next_token();
-
-        if (shcore::str_upper(next).find("SQL_MODE") != std::string::npos) {
-          core_session->refresh_sql_mode();
-          splitter->set_ansi_quotes(core_session->ansi_quotes_enabled());
-          splitter->set_no_backslash_escapes(
-              core_session->no_backslash_escapes_enabled());
-        }
       }
     }
   }
@@ -315,10 +304,14 @@ void Shell_sql::handle_input(std::string &code, Input_state &state) {
       return;
     }
   }
+
   auto session = get_session();
+  if (auto core_session = session->get_core_session()) {
+    m_splitter->set_ansi_quotes(core_session->ansi_quotes_enabled());
+    m_splitter->set_no_backslash_escapes(
+        core_session->no_backslash_escapes_enabled());
+  }
 
-  m_splitter->set_ansi_quotes(ansi_quotes_enabled(session));
-  m_splitter->set_no_backslash_escapes(no_backslash_escapes_enabled(session));
   _last_handled.clear();
 
   // the whole code to be executed is in the `code` variable
diff --git a/mysqlshdk/shellcore/sql_handler_registry.cc b/mysqlshdk/shellcore/sql_handler_registry.cc
index 4e42c6aa9c..239b304ae5 100644
--- a/mysqlshdk/shellcore/sql_handler_registry.cc
+++ b/mysqlshdk/shellcore/sql_handler_registry.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2023, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -48,7 +48,7 @@ void Sql_handler_registry::register_sql_handler(
   if (prefixes.empty()) {
     throw std::runtime_error("At least one prefix must be specified.");
   } else {
-    for (auto prefix : prefixes) {
+    for (const auto &prefix : prefixes) {
       if (shcore::str_strip(prefix).empty()) {
         throw std::runtime_error("Empty or blank prefixes are not allowed.");
       }
@@ -85,7 +85,7 @@ void Sql_handler_registry::register_sql_handler(
 
 Sql_handler_ptr Sql_handler_registry::get_handler_for_sql(
     std::string_view sql) const {
-  auto match = m_sql_handler_matcher.matches(std::string(sql));
+  auto match = m_sql_handler_matcher.matches(sql);
   if (match.has_value()) {
     return m_sql_handlers.at(*match);
   }
diff --git a/packaging/debian/CMakeLists.txt b/packaging/debian/CMakeLists.txt
index 1c45ae1650..4c51075534 100644
--- a/packaging/debian/CMakeLists.txt
+++ b/packaging/debian/CMakeLists.txt
@@ -250,6 +250,7 @@ endforeach()
 file(WRITE "${DEST_DIR}/mysql-shell${PRODUCT_SUFFIX}.install"
 "usr/bin/mysqlsh
 usr/bin/mysql-secret-store-login-path
+usr/bin/mysql-secret-store-secret-service
 usr/share/mysqlsh/LICENSE    usr/share/doc/mysqlsh
 usr/share/mysqlsh/README     usr/share/doc/mysqlsh
 usr/share/mysqlsh/Docs/INFO_SRC     usr/share/mysqlsh/Docs
diff --git a/packaging/rpm/mysql-shell.spec.in b/packaging/rpm/mysql-shell.spec.in
index b84514f0f8..822b52a1fe 100644
--- a/packaging/rpm/mysql-shell.spec.in
+++ b/packaging/rpm/mysql-shell.spec.in
@@ -254,6 +254,7 @@ rm -f %{buildroot}%{_datadir}/mysqlsh/{LICENSE,README}
 %doc README
 %{_bindir}/mysqlsh
 %{_bindir}/mysql-secret-store-login-path
+%{_bindir}/mysql-secret-store-secret-service
 %if 0%{?bundled_mysql_config_editor:1}
 %{_libexecdir}/mysqlsh/mysql_config_editor
 %endif
diff --git a/python/packages/mysql_gadgets/command/sandbox.py b/python/packages/mysql_gadgets/command/sandbox.py
index ede8c13b2d..da7abb1100 100644
--- a/python/packages/mysql_gadgets/command/sandbox.py
+++ b/python/packages/mysql_gadgets/command/sandbox.py
@@ -1,5 +1,5 @@
 #
-# Copyright (c) 2016, 2024, Oracle and/or its affiliates.
+# Copyright (c) 2016, 2025, Oracle and/or its affiliates.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License, version 2.0,
@@ -440,12 +440,24 @@ def create_sandbox(**kwargs):
                                      u"".format(sandbox_dir))
     # If no value is provided for mysqld, search value on PATH and default
     # mysqld paths.
+    mysqld_base_dir = None
     try:
-        mysqld_path = kwargs.get("mysqld_path",
-                                 tools.get_tool_path(
-                                     None, "mysqld", search_path=True,
-                                     required=True,
-                                     check_tool_func=server.is_valid_mysqld))
+        mysqld_path = kwargs.get("mysqld_path")
+
+        # If the value of mysqld_path is a directory, then it will be used as
+        # basedir to search for the binary.
+        if mysqld_path is not None and os.path.isdir(mysqld_path):
+            mysqld_base_dir = mysqld_path
+            mysqld_path = None
+
+        if mysqld_path is None:
+            mysqld_path = tools.get_tool_path(
+                mysqld_base_dir, "mysqld", search_path=True,
+                required=True,
+                check_tool_func=server.is_valid_mysqld)
+
+        # Will use the found mysqld as basedir to locate the other tools
+        mysqld_base_dir = os.path.dirname(mysqld_path)
     except exceptions.GadgetError as err:
         if err.errno == 1:
             raise exceptions.GadgetError(_ERROR_CANNOT_FIND_TOOL.format(
@@ -462,9 +474,10 @@ def create_sandbox(**kwargs):
 
     # If no value is provided for mysqladmin, by default search value on PATH
     mysqladmin_path = kwargs.get("mysqladmin_path",
-                                 tools.get_tool_path(None, "mysqladmin",
+                                 tools.get_tool_path(mysqld_base_dir, "mysqladmin",
                                                      search_path=True,
                                                      required=False))
+
     if not mysqladmin_path:
         raise exceptions.GadgetError(_ERROR_CANNOT_FIND_TOOL.format(
             exec_name="mysqladmin", path_var_name=PATH_ENV_VAR))
@@ -494,8 +507,10 @@ def create_sandbox(**kwargs):
         # If no value is provided for mysql_ssl_rsa_setup, by default search value
         # on PATH
         mysql_ssl_rsa_setup_path = kwargs.get(
-            "mysql_ssl_rsa_setup_path", tools.get_tool_path(
-                None, "mysql_ssl_rsa_setup", search_path=True, required=False))
+            "mysql_ssl_rsa_setup_path", tools.get_tool_path(mysqld_base_dir,
+                                                            "mysql_ssl_rsa_setup",
+                                                            search_path=True,
+                                                            required=False))
 
         if not mysql_ssl_rsa_setup_path and not ignore_ssl_error:
             raise exceptions.GadgetError(
diff --git a/python/packages/mysqlsh/plugin_manager/registrar.py b/python/packages/mysqlsh/plugin_manager/registrar.py
index a76799cd1d..b143ca1761 100644
--- a/python/packages/mysqlsh/plugin_manager/registrar.py
+++ b/python/packages/mysqlsh/plugin_manager/registrar.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2020, 2024, Oracle and/or its affiliates.
+# Copyright (c) 2020, 2025, Oracle and/or its affiliates.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License, version 2.0,
@@ -34,6 +34,12 @@
 # Where definition is an instance of FunctionData
 _registration_callbacks = []
 
+# Function definition registry, enables shell clients
+# to access all plugin function definitions despite their
+# availability configuration (in special web only plugin functions)
+_registry = []
+get_registry = lambda: _registry
+
 
 def add_registration_callback(callback):
     _registration_callbacks.append(callback)
@@ -678,6 +684,9 @@ def register_function(
             cli=cli,
             web=web)
 
+        # Registers definition in the global registry
+        _registry.append(definition)
+
         try:
             if shell:
                 shell_obj.add_extension_object_member(
diff --git a/python/plugins/debug/collect_diagnostics.py b/python/plugins/debug/collect_diagnostics.py
index 267eaada90..f41809dd87 100644
--- a/python/plugins/debug/collect_diagnostics.py
+++ b/python/plugins/debug/collect_diagnostics.py
@@ -25,6 +25,7 @@
 from typing import List, Tuple, Optional
 from mysqlsh import globals
 from mysqlsh import mysql, Error
+import mysqlsh
 import socket
 import zipfile
 import yaml
@@ -111,7 +112,7 @@ def collect_member_info(zf: zipfile.ZipFile, prefix: str, session: InstanceSessi
     if benchmark:
         print("Executing BENCHMARK()...")
         # speed up tests
-        if sys.executable.endswith("mysqlshrec"):
+        if mysqlsh.executable.endswith("mysqlshrec"):
             loop_count = 100
         bm_time = session.run_sql(
             f"SELECT BENCHMARK({loop_count},(1234*5678/37485-1298+8596^2))").get_execution_time()
@@ -383,14 +384,19 @@ def process_path(path: str) -> Tuple[str, str]:
     return path, prefix
 
 
-def do_collect_diagnostics(session_, path, orig_args,
-                           innodbMutex=False,
-                           allMembers=False,
-                           schemaStats=False,
-                           slowQueries=False,
-                           ignoreErrors=False,
-                           customSql: List[str] = [],
-                           customShell: List[str] = []):
+def do_collect_diagnostics(
+    session_,
+    path,
+    orig_args,
+    innodbMutex=False,
+    allMembers=False,
+    schemaStats=False,
+    slowQueries=False,
+    ignoreErrors=False,
+    hostInfo=True,
+    customSql: List[str] = [],
+    customShell: List[str] = [],
+):
     path, prefix = process_path(path)
 
     session = InstanceSession(session_)
@@ -477,7 +483,7 @@ def do_collect_diagnostics(session_, path, orig_args,
                         "Connected to remote server, ping stats not be collected.")
 
             # collect system info
-            if local_target:
+            if local_target and hostInfo:
                 collect_host_info(zf, prefix)
     except:
         if os.path.exists(path):
@@ -494,7 +500,14 @@ def do_collect_diagnostics(session_, path, orig_args,
         print("NOTE: allMembers enabled, but InnoDB Cluster metadata not found")
 
 
-def collect_common_high_load_data(zf: zipfile.ZipFile, prefix: str, session: InstanceSession, info: dict, local_target: bool):
+def collect_common_high_load_data(
+    zf: zipfile.ZipFile,
+    prefix: str,
+    session: InstanceSession,
+    info: dict,
+    local_target: bool,
+    host_info: bool,
+):
     # collect general info
     collect_basic_info(zf, prefix, session,
                        info=info,
@@ -510,17 +523,22 @@ def collect_common_high_load_data(zf: zipfile.ZipFile, prefix: str, session: Ins
                         benchmark=True)
 
     # collect system info
-    if local_target:
+    if local_target and host_info:
         collect_host_info(zf, prefix)
 
 
-def do_collect_high_load_diagnostics(session_, path, orig_args,
-                                     innodbMutex=False,
-                                     iterations=2,
-                                     delay=5*60,
-                                     pfsInstrumentation="current",
-                                     customSql: List[str] = [],
-                                     customShell: List[str] = []):
+def do_collect_high_load_diagnostics(
+    session_,
+    path,
+    orig_args,
+    innodbMutex=False,
+    hostInfo=True,
+    iterations=2,
+    delay=5 * 60,
+    pfsInstrumentation="current",
+    customSql: List[str] = [],
+    customShell: List[str] = [],
+):
     path, prefix = process_path(path)
 
     if iterations < 1:
@@ -546,19 +564,32 @@ def do_collect_high_load_diagnostics(session_, path, orig_args,
     try:
         with zipfile.ZipFile(path, mode="w") as zf:
             os.chmod(path, 0o600)
-            collect_common_high_load_data(zf, prefix, session,
-                                          {"command": "collectHighLoadDiagnostics",
-                                           "commandOptions": normalize(orig_args)},
-                                          local_target)
+            collect_common_high_load_data(
+                zf,
+                prefix,
+                session,
+                {
+                    "command": "collectHighLoadDiagnostics",
+                    "commandOptions": normalize(orig_args),
+                },
+                local_target,
+                host_info=hostInfo,
+            )
 
             assert not customShell or local_target
 
             print()
-            collect_diagnostics(zf, prefix, session,
-                                iterations, delay, pfsInstrumentation,
-                                innodb_mutex=innodbMutex,
-                                custom_sql=customSql,
-                                custom_shell=customShell)
+            collect_diagnostics(
+                zf,
+                prefix,
+                session,
+                iterations,
+                delay,
+                pfsInstrumentation,
+                innodb_mutex=innodbMutex,
+                custom_sql=customSql,
+                custom_shell=customShell,
+            )
     except:
         if os.path.exists(path):
             os.remove(path)
@@ -634,12 +665,18 @@ def execute_profiled_query(zf: zipfile.ZipFile, prefix: str, helper_session, que
     return query_info
 
 
-def do_collect_slow_query_diagnostics(session_, path: str, query: str, orig_args,
-                                      innodbMutex=False,
-                                      delay=15,
-                                      pfsInstrumentation="current",
-                                      customSql: List[str] = [],
-                                      customShell: List[str] = []):
+def do_collect_slow_query_diagnostics(
+    session_,
+    path: str,
+    query: str,
+    orig_args,
+    innodbMutex=False,
+    hostInfo=True,
+    delay=15,
+    pfsInstrumentation="current",
+    customSql: List[str] = [],
+    customShell: List[str] = [],
+):
     if not query:
         raise Error("'query' must contain the query to be analyzed")
     path, prefix = process_path(path)
@@ -679,10 +716,17 @@ def do_collect_slow_query_diagnostics(session_, path: str, query: str, orig_args
                     f"Collecting information for referenced table `{s}`.`{t}`")
                 collect_table_info(zf, prefix, session, s, t)
 
-            collect_common_high_load_data(zf, prefix, session,
-                                          {"command": "collectSlowQueryDiagnostics",
-                                           "commandOptions": normalize(orig_args)},
-                                          local_target)
+            collect_common_high_load_data(
+                zf,
+                prefix,
+                session,
+                {
+                    "command": "collectSlowQueryDiagnostics",
+                    "commandOptions": normalize(orig_args),
+                },
+                local_target,
+                host_info=hostInfo,
+            )
 
             print("Collecting EXPLAIN")
             explain_query(zf, session, query, prefix)
diff --git a/python/plugins/debug/init.py b/python/plugins/debug/init.py
index c255013c9c..30dda298e5 100644
--- a/python/plugins/debug/init.py
+++ b/python/plugins/debug/init.py
@@ -73,6 +73,10 @@ def collect_diagnostics(path: str, **options):
     @li Slow queries (if enabled)
     @li Performance Schema configuration
 
+    When the `hostInfo` option is enabled, various other OS level information
+    from the host is collected using various diagnostic utilities. In Linux
+    and macos, sudo is used to execute some of them.
+
     Replication/InnoDB Cluster
     @li mysql_innodb_cluster_metadata schema and contents
     @li Replication related tables in performance_schema
@@ -107,6 +111,8 @@ def collect_diagnostics(path: str, **options):
             Default false.
         ignoreErrors (bool): If true, ignores query errors during collection.
             Default false.
+        hostInfo (bool): If true and the target server is localhost,
+            collects various host diagnostics info. Default true.
         customSql (list): Custom list of SQL statements to execute.
         customShell (list): Custom list of shell commands to execute.
     """
@@ -139,6 +145,10 @@ def collect_high_load_diagnostics(path: str, **options):
     @li Process list, open tables, host cache
     @li System info collected from the OS (if connected to localhost)
 
+    When the `hostInfo` option is enabled, various other OS level information
+    from the host is collected using various diagnostic utilities. In Linux
+    and macos, sudo is used to execute some of them.
+
     Schema Statistics
     @li Number of schema objects (sys.schema_table_overview)
     @li Full list of tables with basic stats
@@ -175,6 +185,8 @@ def collect_high_load_diagnostics(path: str, **options):
         pfsInstrumentation (string): One of current, medium, full. Controls
             whether additional PERFORMANCE_SCHEMA instruments and consumers are
             temporarily enabled (default 'current').
+        hostInfo (bool): If true and the target server is localhost,
+            collects various host diagnostics info. Default true.
         customSql (list): Custom list of SQL statements to execute.
             If the statement is prefixed with `before:` or nothing, it will be
             executed once, before the metrics collection loop. If prefixed with
@@ -229,6 +241,10 @@ def collect_slow_query_diagnostics(path: str, query: str, **options):
     may provide additional insights, at the cost of an larger impact on server
     performance.
 
+    When the `hostInfo` option is enabled, various other OS level information
+    from the host is collected using various diagnostic utilities. In Linux
+    and macos, sudo is used to execute some of them.
+
     Args:
         path (str): path to write the zip file with diagnostics information
         query (str): query to be analyzed
@@ -243,6 +259,8 @@ def collect_slow_query_diagnostics(path: str, query: str, **options):
         pfsInstrumentation (string): One of current, medium, full. Controls
             whether additional PERFORMANCE_SCHEMA instruments and consumers are
             temporarily enabled (default 'current').
+        hostInfo (bool): If true and the target server is localhost,
+            collects various host diagnostics info. Default true.
         customSql (list): Custom list of SQL statements to execute.
             If the statement is prefixed with `before:` or nothing, it will be
             executed once, before the metrics collection loop. If prefixed with
diff --git a/res/upgrade_checker/sysvars.json b/res/upgrade_checker/sysvars.json
index 706536e757..40d053fa94 100644
--- a/res/upgrade_checker/sysvars.json
+++ b/res/upgrade_checker/sysvars.json
@@ -717,6 +717,21 @@
       }
     ]
   },
+  {
+    "name": "connection_control_failed_connections_threshold",
+    "version": "5.6.35",
+    "deprecation": "9.2.0"
+  },
+  {
+    "name": "connection_control_max_connection_delay",
+    "version": "5.6.35",
+    "deprecation": "9.2.0"
+  },
+  {
+    "name": "connection_control_min_connection_delay",
+    "version": "5.6.35",
+    "deprecation": "9.2.0"
+  },
   {
     "name": "connection_memory_chunk_size",
     "version": "8.0.28",
@@ -2277,6 +2292,71 @@
     "deprecation": "8.0.34",
     "removal": "8.4.0"
   },
+  {
+    "name": "keyring_hashicorp_auth_path",
+    "version": "8.0.18",
+    "deprecation": "9.3.0"
+  },
+  {
+    "name": "keyring_hashicorp_ca_path",
+    "version": "8.0.18",
+    "deprecation": "9.3.0"
+  },
+  {
+    "name": "keyring_hashicorp_caching",
+    "version": "8.0.18",
+    "deprecation": "9.3.0"
+  },
+  {
+    "name": "keyring_hashicorp_commit_auth_path",
+    "version": "8.0.18",
+    "deprecation": "9.3.0"
+  },
+  {
+    "name": "keyring_hashicorp_commit_ca_path",
+    "version": "8.0.18",
+    "deprecation": "9.3.0"
+  },
+  {
+    "name": "keyring_hashicorp_commit_caching",
+    "version": "8.0.18",
+    "deprecation": "9.3.0"
+  },
+  {
+    "name": "keyring_hashicorp_commit_role_id",
+    "version": "8.0.18",
+    "deprecation": "9.3.0"
+  },
+  {
+    "name": "keyring_hashicorp_commit_server_url",
+    "version": "8.0.18",
+    "deprecation": "9.3.0"
+  },
+  {
+    "name": "keyring_hashicorp_commit_store_path",
+    "version": "8.0.18",
+    "deprecation": "9.3.0"
+  },
+  {
+    "name": "keyring_hashicorp_role_id",
+    "version": "8.0.18",
+    "deprecation": "9.3.0"
+  },
+  {
+    "name": "keyring_hashicorp_secret_id",
+    "version": "8.0.18",
+    "deprecation": "9.3.0"
+  },
+  {
+    "name": "keyring_hashicorp_server_url",
+    "version": "8.0.18",
+    "deprecation": "9.3.0"
+  },
+  {
+    "name": "keyring_hashicorp_store_path",
+    "version": "8.0.18",
+    "deprecation": "9.3.0"
+  },
   {
     "name": "keyring_oci_ca_certificate",
     "version": "8.0.22",
@@ -3246,6 +3326,76 @@
             ]
           }
         }
+      },
+      {
+        "version": "9.3.0",
+        "all": {
+          "allowed": {
+            "all": [
+              "batched_key_access={on|off}",
+              "block_nested_loop={on|off}",
+              "condition_fanout_filter={on|off}",
+              "derived_condition_pushdown={on|off}",
+              "derived_merge={on|off}",
+              "duplicateweedout={on|off}",
+              "engine_condition_pushdown={on|off}",
+              "firstmatch={on|off}",
+              "hash_join={on|off}",
+              "hypergraph_optimizer=off",
+              "index_condition_pushdown={on|off}",
+              "index_merge={on|off}",
+              "index_merge_intersection={on|off}",
+              "index_merge_sort_union={on|off}",
+              "index_merge_union={on|off}",
+              "loosescan={on|off}",
+              "materialization={on|off}",
+              "mrr={on|off}",
+              "mrr_cost_based={on|off}",
+              "prefer_ordering_index={on|off}",
+              "semijoin={on|off}",
+              "skip_scan={on|off}",
+              "subquery_materialization_cost_based={on|off}",
+              "subquery_to_derived={on|off}",
+              "use_index_extensions={on|off}",
+              "use_invisible_indexes={on|off}"
+            ]
+          }
+        }
+      },
+      {
+        "version": "9.3.1",
+        "all": {
+          "allowed": {
+            "all": [
+              "batched_key_access={on|off}",
+              "block_nested_loop={on|off}",
+              "condition_fanout_filter={on|off}",
+              "derived_condition_pushdown={on|off}",
+              "derived_merge={on|off}",
+              "duplicateweedout={on|off}",
+              "engine_condition_pushdown={on|off}",
+              "firstmatch={on|off}",
+              "hash_join={on|off}",
+              "hypergraph_optimizer=on",
+              "index_condition_pushdown={on|off}",
+              "index_merge={on|off}",
+              "index_merge_intersection={on|off}",
+              "index_merge_sort_union={on|off}",
+              "index_merge_union={on|off}",
+              "loosescan={on|off}",
+              "materialization={on|off}",
+              "mrr={on|off}",
+              "mrr_cost_based={on|off}",
+              "prefer_ordering_index={on|off}",
+              "semijoin={on|off}",
+              "skip_scan={on|off}",
+              "subquery_materialization_cost_based={on|off}",
+              "subquery_to_derived={on|off}",
+              "use_index_extensions={on|off}",
+              "use_invisible_indexes={on|off}"
+            ]
+          }
+        }
       }
     ]
   },
@@ -4937,12 +5087,14 @@
   {
     "name": "version_tokens_session",
     "version": "5.7.8",
-    "deprecation": "9.2.0"
+    "deprecation": "9.2.0",
+    "removal": "9.3.0"
   },
   {
     "name": "version_tokens_session_number",
     "version": "5.7.8",
-    "deprecation": "9.2.0"
+    "deprecation": "9.2.0",
+    "removal": "9.3.0"
   },
   {
     "name": "performance_schema_consumer_events_transactions_current",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 4020f63d32..88e618ff65 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -344,14 +344,37 @@ ENDIF()
 # to prevent the shell from having different openssl dependencies (system one/bundled one)
 IF(BUNDLED_KRB5_DIR)
   IF(WIN32)
-    FILE(GLOB _KRB5_LIBRARIES "${BUNDLED_KRB5_DIR}/bin/*.dll")
+    SET(_KRB5_LIB_DIR "${BUNDLED_KRB5_DIR}/bin")
+    SET(_KRB5_BUNDLED_LIBS
+      "krb5_64.dll"
+      "k5sprt64.dll"
+      "comerr64.dll"
+      "xpprof64.dll"
+      "krbcc64.dll"
+      "gssapi64.dll"
+    )
   ELSE()
-    FILE(GLOB _KRB5_LIBRARIES "${BUNDLED_KRB5_DIR}/lib/lib*")
+    SET(_KRB5_LIB_DIR "${BUNDLED_KRB5_DIR}/lib")
+    SET(_KRB5_BUNDLED_LIBS
+      "libkrb5.*"
+      "libcom_err.*"
+      "libgssapi_krb5.*"
+      "libk5crypto.*"
+      "libkrb5support.*"
+    )
   ENDIF()
 
-  IF(NOT _KRB5_LIBRARIES)
-    MESSAGE(FATAL_ERROR "Could not find the bundled krb5 libraries")
-  ENDIF()
+  SET(_KRB5_LIBRARIES "")
+
+  FOREACH(_pattern ${_KRB5_BUNDLED_LIBS})
+    FILE(GLOB _libs "${_KRB5_LIB_DIR}/${_pattern}")
+
+    IF(NOT _libs)
+      MESSAGE(FATAL_ERROR "Could not find the bundled krb5 library: ${_pattern}")
+    ENDIF()
+
+    LIST(APPEND _KRB5_LIBRARIES ${_libs})
+  ENDFOREACH()
 
   install_bundled_binaries(
       DESCRIPTION "Kerberos libraries"
@@ -460,5 +483,6 @@ IF (JIT_EXECUTOR_LIB)
       BINARIES "${INSTALL_JIT_EXECUTOR_LIBRARY}"
       DESTINATION "${INSTALL_LIBDIR}"
       TARGET mysqlsh
+      WRITE_RPATH FALSE
   )
 ENDIF()
diff --git a/src/mysqlsh/cmdline_shell.cc b/src/mysqlsh/cmdline_shell.cc
index c33b0b9068..06b7ef2e17 100644
--- a/src/mysqlsh/cmdline_shell.cc
+++ b/src/mysqlsh/cmdline_shell.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2014, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -1180,7 +1180,6 @@ void Command_line_shell::print_cmd_line_helper() {
   println("       mysqlsh [OPTIONS] [URI] -f <path> [<script-args>...]");
   println("       mysqlsh [OPTIONS] [URI] --cluster|--replicaset");
   println("       mysqlsh [OPTIONS] [URI] -- <object> <method> [<method-args>...]");
-  println("       mysqlsh [OPTIONS] [URI] --dba enableXProtocol");
   println("");
   // clang-format on
   std::vector<std::string> details = Shell_options(0, nullptr).get_details();
diff --git a/src/mysqlsh/main.cc b/src/mysqlsh/main.cc
index df118a027c..65849824c6 100644
--- a/src/mysqlsh/main.cc
+++ b/src/mysqlsh/main.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2014, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -62,176 +62,6 @@ void finalize_debug_shell(mysqlsh::Command_line_shell *shell);
 
 const char *g_mysqlsh_path;
 
-static int enable_x_protocol(
-    std::shared_ptr<mysqlsh::Command_line_shell> shell) {
-  auto shell_session = shell->shell_context()->get_dev_session();
-
-  if (!shell_session) {
-    mysqlsh::current_console()->print_error(
-        "A session to the target instance is required to execute "
-        "the enableXProtocol command.\n");
-    return 1;
-  }
-
-  mysqlshdk::db::Connection_options connection_options =
-      shell_session->get_connection_options();
-
-  connection_options.set_scheme("mysqlx");
-  // Temporary port to be replaced by the X port
-  connection_options.set_port(33060);
-
-  std::string temp_uri =
-      connection_options.as_uri(mysqlshdk::db::uri::formats::full());
-
-  shell->switch_shell_mode(shcore::Shell_core::Mode::JavaScript, {});
-  shell->process_line("var uri_template = '" + temp_uri + "'");
-
-  // clang-format off
-  static const char *script =
-      R"*(
-// Returns object with code and message about X Protocol state
-function checkXProtocol() {
-  result = {}
-  result.code = 0;
-  result.message = '';
-
-  // Return codes:
-  // - 0: X Protocol is active for TCP connections
-  // - 1: X Protocol is active but is not listening for TCP connections
-  // - 2: X Protocol is inactive
-  // - 3: Error verifying xprotocol state
-
-  try {
-    if (session.uri.indexOf("mysqlx://") == 0) {
-      let x_port = session.runSql('select @@mysqlx_port as xport').fetchOneObject().xport;
-      result.code = 0;
-      result.message = `X Protocol plugin is already enabled and listening for connections on port ${x_port}`;
-    } else {
-      let active = session.runSql(`SELECT COUNT(*) AS active
-                                    FROM information_schema.plugins WHERE plugin_name='mysqlx'
-                                    AND plugin_status='ACTIVE'`).fetchOneObject().active;
-
-      if (active) {
-        let x_port = session.runSql("SELECT @@mysqlx_port AS xport").fetchOneObject().xport;
-        let server_uuid = session.runSql("SELECT @@server_uuid AS uuid").fetchOneObject().uuid;
-
-        // Attempts a TCP connection to the X protocol
-        let x_uri = uri_template.replace(":33060", ":" + x_port.toString());
-        let x_session;
-        try {
-          x_session = shell.openSession(x_uri);
-          let x_server_uuid = x_session.runSql("SELECT @@server_uuid AS uuid").fetchOneObject().uuid;
-          x_session.close();
-          if (server_uuid != x_server_uuid) {
-            result.code = 1;
-            result.message = 'The X Protocol plugin is enabled, however a different ' +
-                            `server is listening for TCP connections at port ${x_port}` +
-                            ', check the MySQL Server log for more details';
-          } else {
-            result.message = `The X Protocol plugin is already enabled and listening for connections on port ${x_port}`;
-          }
-        } catch (error) {
-          result.code = 1;
-          result.message = 'The X plugin is enabled, however failed to create a session using ' +
-                    `port ${x_port}, check the MySQL Server log for more details`;
-        }
-      } else {
-        result.code = 2;
-        result.message = 'The X plugin is not enabled';
-      }
-    }
-  } catch (error) {
-    result.code = 3;
-    result.message = `Error checking for X Protocol plugin: ${error["message"]}`;
-  }
-
-  return result;
-}
-
-function enableXProtocol() {
-  println('enableXProtocol: Installing plugin mysqlx...');
-  var row = session.runSql("select @@version_compile_os, substr(@@version, 1, instr(@@version, '-')-1)").fetchOne();
-  var version = row[1].split(".");
-  var vernum = parseInt(version[0]) * 10000 + parseInt(version[1]) * 100 + parseInt(version[2]);
-  let error_str = '';
-
-  if (row[0] == "Win32" || row[0] == "Win64") {
-    var r = session.runSql("install plugin mysqlx soname 'mysqlx.dll';");
-    if (vernum >= 80004)
-      var r = session.runSql("install plugin mysqlx_cache_cleaner soname 'mysqlx.dll';");
-  } else {
-    var r = session.runSql("install plugin mysqlx soname 'mysqlx.so';")
-    if (vernum >= 80004)
-      var r = session.runSql("install plugin mysqlx_cache_cleaner soname 'mysqlx.so';");
-  }
-  println('enableXProtocol: Verifying plugin is active...')
-  let active = session.runSql(`SELECT COUNT(*) AS active
-                                FROM information_schema.plugins WHERE plugin_name='mysqlx'
-                                AND plugin_status='ACTIVE'`).fetchOneObject().active;
-
-  return error_str;
-}
-
-// Initial verification for X protocol state
-let state = checkXProtocol(session);
-
-// If it is OK or there was an error verifying there's nothing else to do.
-if (state.code == 0 || state.code == 3) {
-  println(`enableXProtocol: ${state.message}`);
-} else {
-  try {
-    if (state.code == 2) {
-      error = enableXProtocol();
-
-      if (error == '') {
-        state = checkXProtocol();
-      }
-    }
-
-    switch (state.code) {
-      case 0:
-        println('enableXProtocol: successfully installed the X protocol plugin!');
-        break;
-      case 1:
-        println(`enableXProtocol: WARNING: ${state.message}`);
-        break;
-      case 2:
-        throw state.message;
-        break;
-      case 3:
-        println(`enableXProtocol: Error verifying X protocol state: ${state.message}`);
-        break;
-    }
-  } catch (error) {
-    println('Error installing the X Plugin: '+ error['message']);
-  }
-}
-println();
-)*";
-  // clang-format on
-  std::stringstream stream(script);
-  return shell->process_stream(stream, "(command line)", {});
-}
-
-// Execute a Administrative DB command passed from the command line via the
-// --dba option Currently, only the enableXProtocol command is supported.
-int execute_dba_command(std::shared_ptr<mysqlsh::Command_line_shell> shell,
-                        const std::string &command) {
-  if (command != "enableXProtocol") {
-    mysqlsh::current_console()->raw_print(
-        "Unsupported dba command " + command + "\n",
-        mysqlsh::Output_stream::STDERR);
-    return 1;
-  }
-
-  // this is a temporary solution, ideally there will be a dba object/module
-  // that implements all commands are the requested command will be invoked as
-  // dba.command() with param handling and others, from both interactive shell
-  // and cmdline
-
-  return enable_x_protocol(shell);
-}
-
 // Detects whether the shell will be running in interactive mode or not
 // Non interactive mode is used when:
 // - A file is processed using the --file option
@@ -262,7 +92,6 @@ void detect_interactive(mysqlsh::Shell_options *options, bool *stdin_is_tty,
     is_interactive = options->get().run_file.empty() &&
                      options->get().run_module.empty() &&
                      options->get().execute_statement.empty() &&
-                     options->get().execute_dba_statement.empty() &&
                      !options->get_shell_cli_operation();
 
   // The --interactive option forces the shell to work emulating the
@@ -864,16 +693,6 @@ int main(int argc, char **argv) {
         std::stringstream stream(options.execute_statement);
         ret_val = shell->process_stream(stream, "(command line)",
                                         options.script_argv);
-      } else if (!options.execute_dba_statement.empty()) {
-        if (options.initial_mode != shcore::IShell_core::Mode::JavaScript &&
-            options.initial_mode != shcore::IShell_core::Mode::None) {
-          mysqlsh::current_console()->raw_print(
-              "The --dba option can only be used in JavaScript mode\n",
-              mysqlsh::Output_stream::STDERR);
-          ret_val = 1;
-        } else {
-          ret_val = execute_dba_command(shell, options.execute_dba_statement);
-        }
       } else if (!options.run_file.empty()) {
         ret_val = shell->process_file(options.run_file, options.script_argv);
       } else if (!options.run_module.empty()) {
diff --git a/src/mysqlsh/mysql_shell.cc b/src/mysqlsh/mysql_shell.cc
index a14dcfdae1..cd2778d37c 100644
--- a/src/mysqlsh/mysql_shell.cc
+++ b/src/mysqlsh/mysql_shell.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2014, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -1076,7 +1076,11 @@ std::shared_ptr<mysqlsh::ShellBaseSession> Mysql_shell::connect(
       old_session->close();
     }
 
-    new_session->enable_sql_mode_tracking();
+    try {
+      new_session->track_system_variable("sql_mode");
+    } catch (const mysqlshdk::db::Error &e) {
+      // can throw for e.g. ER_MUST_CHANGE_PASSWORD which we should ignore
+    }
   }
 
   if (interactive) {
@@ -1990,14 +1994,11 @@ bool Mysql_shell::reconnect_if_needed(bool force) {
 
     while (!ret_val && attempts > 0 && !interrupt.test()) {
       try {
-        const auto &ssh_data = co.get_ssh_options();
-        // we need to increment port usage and decrement it later cause during
-        // reconnect, the decrement will be called, so tunnel would be lost
-        mysqlshdk::ssh::current_ssh_manager()->port_usage_increment(ssh_data);
-
-        shcore::Scoped_callback scoped([&ssh_data] {
-          mysqlshdk::ssh::current_ssh_manager()->port_usage_decrement(ssh_data);
-        });
+        // we need to hold the reference to the SSH tunnel while we reconnect,
+        // to prevent it from being closed when the session closes
+        const auto ssh = mysqlshdk::ssh::current_ssh_manager()->get_tunnel(
+            co.get_ssh_options());
+
         session->connect(co);
         ret_val = true;
       } catch (const shcore::Exception &e) {
@@ -2087,11 +2088,13 @@ void Mysql_shell::add_devapi_completions() {
                                   {"commit", "SqlResult", true},
                                   {"createSchema", "Schema", true},
                                   {"dropSchema", "", true},
+                                  {"getClientData", "", true},
                                   {"getConnectionId", "Schema", true},
                                   {"getCurrentSchema", "Schema", true},
                                   {"getDefaultSchema", "Schema", true},
                                   {"getSchema", "Schema", true},
                                   {"getSchemas", "", true},
+                                  {"getSqlMode", "", true},
                                   {"getSshUri", "", true},
                                   {"getUri", "", true},
                                   {"help", "", true},
@@ -2103,6 +2106,7 @@ void Mysql_shell::add_devapi_completions() {
                                   {"setFetchWarnings", "Result", true},
                                   {"sql", "SqlOperation*", true},
                                   {"startTransaction", "SqlResult", true},
+                                  {"setClientData", "", true},
                                   {"setSavepoint", "", true},
                                   {"releaseSavepoint", "", true},
                                   {"rollbackTo", "", true},
@@ -2493,15 +2497,19 @@ void Mysql_shell::add_devapi_completions() {
   registry->add_completable_type("ClassicSession",
                                  {{"close", "", true},
                                   {"commit", "ClassicResult", true},
+                                  {"getClientData", "", true},
                                   {"getConnectionId", "", true},
                                   {"getUri", "", true},
+                                  {"getSqlMode", "", true},
                                   {"getSshUri", "", true},
                                   {"help", "", true},
                                   {"isOpen", "", true},
                                   {"rollback", "ClassicResult", true},
                                   {"runSql", "ClassicResult", true},
                                   {"startTransaction", "ClassicResult", true},
+                                  {"setClientData", "", true},
                                   {"setQueryAttributes", "", true},
+                                  {"trackSystemVariable", "", true},
                                   {"connectionId", "", false},
                                   {"uri", "", false},
                                   {"sshUri", "", false},
diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt
index 11a8d37b15..69ed2d3cb3 100644
--- a/unittest/CMakeLists.txt
+++ b/unittest/CMakeLists.txt
@@ -193,6 +193,7 @@ if(WITH_TESTS)
     get_property(run_unit_tests_SOURCES TARGET run_unit_tests PROPERTY SOURCES)
     source_group(TREE "${PROJECT_SOURCE_DIR}" FILES ${run_unit_tests_SOURCES})
 
+    add_subdirectory(kdestroy)
 else()
   message(WARNING "Skipping tests. To enable unit-tests use -DWITH_TESTS=1")
 endif()
diff --git a/unittest/auto_script_js_t.cc b/unittest/auto_script_js_t.cc
index 3fb5dab705..5380ab6851 100644
--- a/unittest/auto_script_js_t.cc
+++ b/unittest/auto_script_js_t.cc
@@ -231,6 +231,9 @@ class Auto_script_js : public Shell_js_script_tester,
              ";return true;} catch(e) {return false;}})();";
       execute(code);
     }
+
+    code = "var __has_util_plugin = 'visual' in util;";
+    execute(code);
   }
 
   void run_and_check() {
@@ -370,6 +373,14 @@ constexpr auto k_third_password = "retroper";
 class Credential_store_test : public Auto_script_js {
  protected:
   void SetUp() override {
+    if (Shell_test_env::get_target_server_version() <
+        mysqlshdk::utils::k_shell_version) {
+      // this is needed to prevent gtest from invoking the body of a test
+      GTEST_SKIP();
+      SKIP_TEST("This test is only executed once, with latest server");
+      return;
+    }
+
     prepare_session();
     Auto_script_js::SetUp();
 
@@ -384,6 +395,10 @@ class Credential_store_test : public Auto_script_js {
   }
 
   void TearDown() override {
+    if (this->IsSkipped()) {
+      return;
+    }
+
     execute(
         "(function(){var a = shell.listCredentialHelpers();"
         "for (var i = 0; i < a.length; ++i) {"
diff --git a/unittest/auto_script_py_t.cc b/unittest/auto_script_py_t.cc
index cd63458a69..f84ac42769 100644
--- a/unittest/auto_script_py_t.cc
+++ b/unittest/auto_script_py_t.cc
@@ -207,6 +207,9 @@ class Auto_script_py : public Shell_py_script_tester,
              "' in globals()";
       execute(code);
     }
+
+    code = "__has_util_plugin = hasattr(util, 'visual')";
+    execute(code);
   }
 
   void run_and_check() {
diff --git a/unittest/command_line_connection_t.cc b/unittest/command_line_connection_t.cc
index 653520e821..34ef3844c3 100644
--- a/unittest/command_line_connection_t.cc
+++ b/unittest/command_line_connection_t.cc
@@ -1,4 +1,4 @@
-/* Copyright (c) 2017, 2024, Oracle and/or its affiliates.
+/* Copyright (c) 2017, 2025, Oracle and/or its affiliates.
 
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License, version 2.0,
@@ -215,19 +215,19 @@ TEST_F(Command_line_connection_test, session_cmdline_options) {
   std::string uri_xscheme_db = "mysqlx://" + _uri + "/mysql";
   std::string mysql_uri_scheme_db = "mysql://" + _mysql_uri + "/mysql";
 
-  // FR2_5 : mysqlsh -u <user> -p --port=<mysql_port> -ma
+  // FR2_5 : mysqlsh -u <user> -p --port=<mysql_port>
   execute(
-      {_mysqlsh, "-u", _user.c_str(), "-p", mysql_port.c_str(), "-ma",
+      {_mysqlsh, "-u", _user.c_str(), "-p", mysql_port.c_str(),
        "--interactive=full", "--passwords-from-stdin", "-e", "\\status", NULL},
       _pwd.c_str());
 
   MY_EXPECT_CMD_OUTPUT_CONTAINS("Creating a session to '");
   MY_EXPECT_CMD_OUTPUT_CONTAINS("Protocol version:             Classic 10");
 
-  // FR2_7 : mysqlsh -u <user> -p --port=<mysqlx_port> -ma
+  // FR2_7 : mysqlsh -u <user> -p --port=<mysqlx_port>
   execute(
-      {_mysqlsh, "-u", _user.c_str(), "-p", port.c_str(), "-ma",
-       "--interactive=full", "--passwords-from-stdin", "-e", "\\status", NULL},
+      {_mysqlsh, "-u", _user.c_str(), "-p", port.c_str(), "--interactive=full",
+       "--passwords-from-stdin", "-e", "\\status", NULL},
       _pwd.c_str());
 
   MY_EXPECT_CMD_OUTPUT_CONTAINS("Creating a session to '");
@@ -240,14 +240,6 @@ TEST_F(Command_line_connection_test, session_cmdline_options) {
   MY_EXPECT_CMD_OUTPUT_CONTAINS("Creating an X protocol session to '");
   MY_EXPECT_CMD_OUTPUT_CONTAINS("Protocol version:             X protocol");
 
-  // BUG27363459: Deprecation of -mx
-  execute({_mysqlsh, "--uri", uri_scheme_db.c_str(), "-mx",
-           "--interactive=full", "-e", "\\status", NULL});
-
-  MY_EXPECT_CMD_OUTPUT_CONTAINS(
-      "The -mx option was deprecated, please use --mx instead. (Option "
-      "has been processed as --mx).");
-
   // FR_EXTRA_11 : mysqlsh --uri mysqlx://user@host:3306/db --mysqlx
   execute({_mysqlsh, "--uri", mysql_uri_xscheme_db.c_str(), "--mysqlx",
            "--interactive=full", "-e", "\\status", NULL});
@@ -268,17 +260,9 @@ TEST_F(Command_line_connection_test, session_cmdline_options) {
       "Requested session assumes MySQL X Protocol but '" + _host + ":" +
           _mysql_port + "' seems to speak the classic MySQL protocol");
 
-  // FR_EXTRA_15 : mysqlsh --uri mysqlx://user@host:33060/db --mysqlx -ma
-  execute({_mysqlsh, "--uri", uri_xscheme_db.c_str(), "--mysqlx", "-ma",
+  // FR_EXTRA_16 : mysqlsh --uri mysqlx://user@host:33060/db --mx
+  execute({_mysqlsh, "--uri", uri_xscheme_db.c_str(), "--mx",
            "--interactive=full", "-e", "\\status", NULL});
-
-  MY_EXPECT_CMD_OUTPUT_CONTAINS("Creating an X protocol session to ");
-
-  // FR_EXTRA_16 : mysqlsh --uri mysqlx://user@host:33060/db --mx -ma
-  // BUG27363459: Deprecation of -mc
-  execute({_mysqlsh, "--uri", uri_xscheme_db.c_str(), "--mx", "-ma",
-           "--interactive=full", "-e", "\\status", NULL});
-  MY_EXPECT_CMD_OUTPUT_CONTAINS("The -ma option was deprecated.");
   MY_EXPECT_CMD_OUTPUT_CONTAINS("Creating an X protocol session to ");
 
   // FR_EXTRA_19 : mysqlsh --uri user@host:3306/db --mysqlx
@@ -299,20 +283,9 @@ TEST_F(Command_line_connection_test, session_cmdline_options) {
       "Requested session assumes MySQL X Protocol but '" + _host + ":" +
           _mysql_port + "' seems to speak the classic MySQL protocol");
 
-  // FR_EXTRA_SUCCEED_1 : mysqlsh --uri mysql://user@host:3306/db -ma
-  execute({_mysqlsh, "--uri", mysql_uri_scheme_db.c_str(), "-ma",
-           "--interactive=full", "-e", "\\status", NULL});
-
-  MY_EXPECT_CMD_OUTPUT_CONTAINS("Creating a Classic session to ");
-  MY_EXPECT_CMD_OUTPUT_CONTAINS("Protocol version:             Classic 10");
-
-  // BUG27363459: Deprecation of -mc
-  execute({_mysqlsh, mysql_uri_db.c_str(), "-mc", "--interactive=full", "-e",
-           "\\status", NULL});
-
-  MY_EXPECT_CMD_OUTPUT_CONTAINS(
-      "The -mc option was deprecated, please use --mc instead. (Option "
-      "has been processed as --mc).");
+  // FR_EXTRA_SUCCEED_1 : mysqlsh --uri mysql://user@host:3306/db
+  execute({_mysqlsh, "--uri", mysql_uri_scheme_db.c_str(), "--interactive=full",
+           "-e", "\\status", NULL});
 
   MY_EXPECT_CMD_OUTPUT_CONTAINS("Creating a Classic session to ");
   MY_EXPECT_CMD_OUTPUT_CONTAINS("Protocol version:             Classic 10");
@@ -324,8 +297,8 @@ TEST_F(Command_line_connection_test, session_cmdline_options) {
   MY_EXPECT_CMD_OUTPUT_CONTAINS("Creating a Classic session to ");
   MY_EXPECT_CMD_OUTPUT_CONTAINS("Protocol version:             Classic 10");
 
-  // FR_EXTRA_SUCCEED_3 : mysqlsh --uri mysqlx://user@host:33060/db -ma
-  execute({_mysqlsh, uri_xscheme_db.c_str(), "-ma", "--interactive=full", "-e",
+  // FR_EXTRA_SUCCEED_3 : mysqlsh --uri mysqlx://user@host:33060/db
+  execute({_mysqlsh, uri_xscheme_db.c_str(), "--interactive=full", "-e",
            "\\status", NULL});
 
   MY_EXPECT_CMD_OUTPUT_CONTAINS("Creating an X protocol session to ");
@@ -863,4 +836,35 @@ TEST_F(Command_line_connection_test, kerberos_auth_mode) {
 #endif
 }
 
+TEST_F(Command_line_connection_test, local_infile) {
+  // X protocol is not supported
+  execute({_mysqlsh, _uri.c_str(), "--local-infile", "--interactive=full",
+           nullptr});
+  MY_EXPECT_CMD_OUTPUT_CONTAINS("Creating a session to '" + _uri_nopasswd +
+                                "?local-infile=true'");
+  MY_EXPECT_CMD_OUTPUT_CONTAINS(
+      "X Protocol: LOAD DATA LOCAL INFILE is not supported.");
+
+  // classic protocol is supported
+  execute({_mysqlsh, _mysql_uri.c_str(), "--local-infile", "--interactive=full",
+           "-e", "\\status", nullptr});
+  MY_EXPECT_CMD_OUTPUT_CONTAINS("Creating a session to '" +
+                                _mysql_uri_nopasswd + "?local-infile=true'");
+  MY_EXPECT_CMD_OUTPUT_CONTAINS("Connection Id:");
+
+  // last option wins, --local-infile is last
+  execute({_mysqlsh, (_mysql_uri + "?local-infile=0").c_str(), "--local-infile",
+           "--interactive=full", "-e", "\\status", nullptr});
+  MY_EXPECT_CMD_OUTPUT_CONTAINS("Creating a session to '" +
+                                _mysql_uri_nopasswd + "?local-infile=true'");
+  MY_EXPECT_CMD_OUTPUT_CONTAINS("Connection Id:");
+
+  // last option wins, URI is last
+  execute({_mysqlsh, "--local-infile", (_mysql_uri + "?local-infile=0").c_str(),
+           "--interactive=full", "-e", "\\status", nullptr});
+  MY_EXPECT_CMD_OUTPUT_CONTAINS("Creating a session to '" +
+                                _mysql_uri_nopasswd + "?local-infile=0'");
+  MY_EXPECT_CMD_OUTPUT_CONTAINS("Connection Id:");
+}
+
 }  // namespace tests
diff --git a/unittest/completion_frontend_t.cc b/unittest/completion_frontend_t.cc
index 297ce2c71d..7ed35b928d 100644
--- a/unittest/completion_frontend_t.cc
+++ b/unittest/completion_frontend_t.cc
@@ -704,7 +704,9 @@ TEST_F(Completer_frontend, js_shell) {
                                        "createExtensionObject()",
                                        "createResult()",
                                        "deleteAllCredentials()",
+                                       "deleteAllSecrets()",
                                        "deleteCredential()",
+                                       "deleteSecret()",
                                        "disablePager()",
                                        "disconnect()",
                                        "dumpRows()",
@@ -713,6 +715,7 @@ TEST_F(Completer_frontend, js_shell) {
                                        "help()",
                                        "listCredentialHelpers()",
                                        "listCredentials()",
+                                       "listSecrets()",
                                        "listSqlHandlers()",
                                        "listSshConnections()",
                                        "log()",
@@ -720,6 +723,7 @@ TEST_F(Completer_frontend, js_shell) {
                                        "options",
                                        "parseUri()",
                                        "prompt()",
+                                       "readSecret()",
                                        "reconnect()",
                                        "registerGlobal()",
                                        "registerReport()",
@@ -729,6 +733,7 @@ TEST_F(Completer_frontend, js_shell) {
                                        "setSession()",
                                        "status()",
                                        "storeCredential()",
+                                       "storeSecret()",
                                        "unparseUri()",
                                        "version"}));
 
@@ -802,8 +807,9 @@ TEST_F(Completer_frontend, js_devapi) {
 
   EXPECT_AFTER_TAB_TAB(
       "session.get",
-      strv({"getConnectionId()", "getCurrentSchema()", "getDefaultSchema()",
-            "getSchema()", "getSchemas()", "getSshUri()", "getUri()"}));
+      strv({"getClientData()", "getConnectionId()", "getCurrentSchema()",
+            "getDefaultSchema()", "getSchema()", "getSchemas()", "getSqlMode()",
+            "getSshUri()", "getUri()"}));
   EXPECT_AFTER_TAB("session.getCu", "session.getCurrentSchema()");
 
   // TS_FR5.1_X01
@@ -1111,6 +1117,9 @@ TEST_F(Completer_frontend, js_devapi_members_x) {
   std::vector<std::pair<std::string, std::string>> session_calls{
       {"createSchema", "('dropme2')"},
       {"dropSchema", "('dropme2')"},
+      {"getClientData", "('test')"},
+      {"setClientData", "('test', 123)"},
+      {"getSqlMode", "()"},
       {"setCurrentSchema", "('actest')"},
       {"getSchema", "('actest')"},
       {"setFetchWarnings", "(true)"},
@@ -1175,6 +1184,7 @@ TEST_F(Completer_frontend, js_devapi_members_classic) {
       {"unquoteIdentifier", "(`a`)"},
       {"splitScript", "('a;b;')"},
       {"parseStatementAst", "('select 1')"},
+      {"tokenizeStatement", "('select 1')"},
       {"help", ""},
       {"getClassicSession", "('" + _mysql_uri + "')"},
       {"getSession", "('" + _mysql_uri + "')"}};
@@ -1186,7 +1196,10 @@ TEST_F(Completer_frontend, js_devapi_members_classic) {
       {"close", ""},
       {"runSql", "('select 1')"},
       {"setQueryAttributes", "({})"},
-      {"_getSocketFd", ""}};
+      {"_getSocketFd", ""},
+      {"setClientData", "('key', 4)"},
+      {"getClientData", "('key')"},
+      {"trackSystemVariable", "('sql_mode')"}};
   CHECK_OBJECT_MEMBER_COMPLETIONS("session", session_calls);
 }
 #endif
@@ -1249,7 +1262,9 @@ TEST_F(Completer_frontend, py_shell) {
                                        "create_extension_object()",
                                        "create_result()",
                                        "delete_all_credentials()",
+                                       "delete_all_secrets()",
                                        "delete_credential()",
+                                       "delete_secret()",
                                        "disable_pager()",
                                        "disconnect()",
                                        "dump_rows()",
@@ -1258,6 +1273,7 @@ TEST_F(Completer_frontend, py_shell) {
                                        "help()",
                                        "list_credential_helpers()",
                                        "list_credentials()",
+                                       "list_secrets()",
                                        "list_sql_handlers()",
                                        "list_ssh_connections()",
                                        "log()",
@@ -1265,6 +1281,7 @@ TEST_F(Completer_frontend, py_shell) {
                                        "options",
                                        "parse_uri()",
                                        "prompt()",
+                                       "read_secret()",
                                        "reconnect()",
                                        "register_global()",
                                        "register_report()",
@@ -1274,6 +1291,7 @@ TEST_F(Completer_frontend, py_shell) {
                                        "set_session()",
                                        "status()",
                                        "store_credential()",
+                                       "store_secret()",
                                        "unparse_uri()",
                                        "version"}));
 
@@ -1357,10 +1375,11 @@ TEST_F(Completer_frontend, py_devapi) {
   EXPECT_AFTER_TAB("session.sql(mkquery(\"\")).e",
                    "session.sql(mkquery(\"\")).execute()");
 
-  EXPECT_AFTER_TAB_TAB("session.get_",
-                       strv({"get_connection_id()", "get_current_schema()",
-                             "get_default_schema()", "get_schema()",
-                             "get_schemas()", "get_ssh_uri()", "get_uri()"}));
+  EXPECT_AFTER_TAB_TAB(
+      "session.get_",
+      strv({"get_client_data()", "get_connection_id()", "get_current_schema()",
+            "get_default_schema()", "get_schema()", "get_schemas()",
+            "get_sql_mode()", "get_ssh_uri()", "get_uri()"}));
   EXPECT_AFTER_TAB("session.get_cu", "session.get_current_schema()");
 
   // TS_FR5.1_X01
diff --git a/unittest/interactive_shell_t.cc b/unittest/interactive_shell_t.cc
index b82536a923..ca5a09a67b 100644
--- a/unittest/interactive_shell_t.cc
+++ b/unittest/interactive_shell_t.cc
@@ -1,4 +1,4 @@
-/* Copyright (c) 2014, 2024, Oracle and/or its affiliates.
+/* Copyright (c) 2014, 2025, Oracle and/or its affiliates.
 
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License, version 2.0,
@@ -368,9 +368,9 @@ TEST_F(Interactive_shell_test, shell_command_connect_auto) {
     execute("session.close()");
   }
 
-  // FR_EXTRA_SUCCEED_5 : \connect -ma mysql://user@host:3306/db
+  // FR_EXTRA_SUCCEED_5 : \connect mysql://user@host:3306/db
   {
-    execute("\\connect -ma mysql://" + _mysql_uri + "/mysql");
+    execute("\\connect mysql://" + _mysql_uri + "/mysql");
     MY_EXPECT_STDOUT_CONTAINS("Creating a Classic session to '" +
                               _mysql_uri_nopasswd + "/mysql'");
     MY_EXPECT_STDOUT_CONTAINS("Your MySQL connection id is ");
@@ -384,9 +384,9 @@ TEST_F(Interactive_shell_test, shell_command_connect_auto) {
     execute("session.close()");
   }
 
-  // FR_EXTRA_SUCCEED_6 : "\c -ma mysql://user@host:3306/db
+  // FR_EXTRA_SUCCEED_6 : "\c mysql://user@host:3306/db
   {
-    execute("\\c -ma mysql://" + _mysql_uri + "/mysql");
+    execute("\\c mysql://" + _mysql_uri + "/mysql");
     MY_EXPECT_STDOUT_CONTAINS("Creating a Classic session to '" +
                               _mysql_uri_nopasswd + "/mysql'");
     MY_EXPECT_STDOUT_CONTAINS("Your MySQL connection id is ");
@@ -400,9 +400,9 @@ TEST_F(Interactive_shell_test, shell_command_connect_auto) {
     execute("session.close()");
   }
 
-  // FR_EXTRA_SUCCEED_9 : \connect -ma mysqlx://user@host:33060/db
+  // FR_EXTRA_SUCCEED_9 : \connect mysqlx://user@host:33060/db
   {
-    execute("\\connect -ma mysqlx://" + _uri + "/mysql");
+    execute("\\connect mysqlx://" + _uri + "/mysql");
     MY_EXPECT_STDOUT_CONTAINS("Creating an X protocol session to '" +
                               _uri_nopasswd + "/mysql'");
     MY_EXPECT_STDOUT_CONTAINS("Your MySQL connection id is ");
@@ -416,9 +416,9 @@ TEST_F(Interactive_shell_test, shell_command_connect_auto) {
     execute("session.close()");
   }
 
-  // FR_EXTRA_SUCCEED_10 : \c -ma mysqlx://user@host:33060/db
+  // FR_EXTRA_SUCCEED_10 : \c mysqlx://user@host:33060/db
   {
-    execute("\\c -ma mysqlx://" + _uri + "/mysql");
+    execute("\\c mysqlx://" + _uri + "/mysql");
     MY_EXPECT_STDOUT_CONTAINS("Creating an X protocol session to '" +
                               _uri_nopasswd + "/mysql'");
     MY_EXPECT_STDOUT_CONTAINS("Your MySQL connection id is ");
@@ -450,75 +450,6 @@ TEST_F(Interactive_shell_test, shell_command_connect_auto) {
     execute("session.close()");
   }
 
-  // Using -ma for X protocol session(type determined from connection success)
-  {
-    execute("\\connect -ma " + _uri);
-    MY_EXPECT_STDOUT_CONTAINS("Creating a session to '" + _uri_nopasswd + "'");
-    MY_EXPECT_STDOUT_CONTAINS("Your MySQL connection id is ");
-    MY_EXPECT_STDOUT_CONTAINS("(X protocol)");
-    MY_EXPECT_STDOUT_CONTAINS(
-        "No default schema selected; type \\use <schema> to set one.");
-    output_handler.wipe_all();
-
-    execute("session");
-    MY_EXPECT_STDOUT_CONTAINS("<Session:" + _uri_nopasswd);
-    output_handler.wipe_all();
-
-    execute("session.close()");
-  }
-
-  // Using -ma for Classic session(type determined from connection success)
-  {
-    execute("\\connect -ma " + _mysql_uri);
-    MY_EXPECT_STDOUT_CONTAINS("Creating a session to '" + _mysql_uri_nopasswd +
-                              "'");
-    MY_EXPECT_STDOUT_CONTAINS("Your MySQL connection id is ");
-    MY_EXPECT_STDOUT_CONTAINS(
-        "No default schema selected; type \\use <schema> to set one.");
-    output_handler.wipe_all();
-
-    execute("session");
-    MY_EXPECT_STDOUT_CONTAINS("<ClassicSession:" + _mysql_uri_nopasswd);
-    output_handler.wipe_all();
-
-    execute("session.close()");
-  }
-
-  // Using -ma for session type determined by the URI scheme(Classic session)
-  {
-    execute("\\connect -ma mysql://" + _mysql_uri);
-    MY_EXPECT_STDOUT_CONTAINS("Creating a Classic session to '" +
-                              _mysql_uri_nopasswd + "'");
-    MY_EXPECT_STDOUT_CONTAINS("Your MySQL connection id is ");
-    MY_EXPECT_STDOUT_CONTAINS(
-        "No default schema selected; type \\use <schema> to set one.");
-    output_handler.wipe_all();
-
-    execute("session");
-    MY_EXPECT_STDOUT_CONTAINS("<ClassicSession:" + _mysql_uri_nopasswd);
-    output_handler.wipe_all();
-
-    execute("session.close()");
-  }
-
-  // Using -ma for session type determined by the URI scheme(X protocol session)
-  {
-    execute("\\connect -ma mysqlx://" + _uri);
-    MY_EXPECT_STDOUT_CONTAINS("Creating an X protocol session to '" +
-                              _uri_nopasswd + "'");
-    MY_EXPECT_STDOUT_CONTAINS("Your MySQL connection id is ");
-    MY_EXPECT_STDOUT_CONTAINS("(X protocol)");
-    MY_EXPECT_STDOUT_CONTAINS(
-        "No default schema selected; type \\use <schema> to set one.");
-    output_handler.wipe_all();
-
-    execute("session");
-    MY_EXPECT_STDOUT_CONTAINS("<Session:" + _uri_nopasswd);
-    output_handler.wipe_all();
-
-    execute("session.close()");
-  }
-
 #ifndef _WIN32
   {
     auto data = mysqlshdk::db::Connection_options(_uri);
@@ -797,26 +728,6 @@ TEST_F(Interactive_shell_test, shell_command_connect_conflicts) {
   output_handler.wipe_all();
 }
 
-TEST_F(Interactive_shell_test, shell_command_connect_deprecated_options) {
-  // Deprecated is not the same as removed, so these are supposed to still
-  // work in 8.0
-  execute("\\connect -mx " + _uri);
-  MY_EXPECT_STDERR_CONTAINS(
-      "WARNING: The -mx option was deprecated, please use --mx instead.");
-  MY_EXPECT_STDOUT_CONTAINS("Creating an X");
-  output_handler.wipe_all();
-
-  execute("\\connect -mc " + _mysql_uri);
-  MY_EXPECT_STDERR_CONTAINS(
-      "WARNING: The -mc option was deprecated, please use --mc instead.");
-  MY_EXPECT_STDOUT_CONTAINS("Creating a Classic");
-  output_handler.wipe_all();
-
-  execute("\\connect -ma " + _mysql_uri);
-  MY_EXPECT_STDERR_CONTAINS("WARNING: The -ma option was deprecated.");
-  output_handler.wipe_all();
-}
-
 TEST_F(Interactive_shell_test, shell_command_use) {
   execute("\\use mysql");
   MY_EXPECT_STDERR_CONTAINS("Not connected.");
diff --git a/unittest/kdestroy/CMakeLists.txt b/unittest/kdestroy/CMakeLists.txt
new file mode 100644
index 0000000000..4e37bc87d9
--- /dev/null
+++ b/unittest/kdestroy/CMakeLists.txt
@@ -0,0 +1,35 @@
+# Copyright (c) 2025, Oracle and/or its affiliates.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License, version 2.0,
+# as published by the Free Software Foundation.
+#
+# This program is designed to work with certain software (including
+# but not limited to OpenSSL) that is licensed under separate terms,
+# as designated in a particular file or component or in included license
+# documentation.  The authors of MySQL hereby grant you an additional
+# permission to link the program and your derivative works with the
+# separately licensed software that they have either included with
+# the program or referenced in the documentation.
+#
+# This program is distributed in the hope that it will be useful,  but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
+# the GNU General Public License, version 2.0, for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+if(WIN32 OR APPLE)
+  return()
+endif()
+
+add_shell_executable(kdestroy kdestroy.cc TRUE)
+
+if(BUNDLED_KRB5_DIR)
+  target_include_directories(kdestroy SYSTEM PRIVATE "${BUNDLED_KRB5_DIR}/include")
+  target_link_directories(kdestroy PRIVATE "${BUNDLED_KRB5_DIR}/lib")
+endif()
+
+target_link_libraries(kdestroy krb5 k5crypto com_err)
diff --git a/unittest/kdestroy/kdestroy.cc b/unittest/kdestroy/kdestroy.cc
new file mode 100644
index 0000000000..22e8d3ab50
--- /dev/null
+++ b/unittest/kdestroy/kdestroy.cc
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2025, Oracle and/or its affiliates.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2.0,
+ * as published by the Free Software Foundation.
+ *
+ * This program is designed to work with certain software (including
+ * but not limited to OpenSSL) that is licensed under separate terms,
+ * as designated in a particular file or component or in included license
+ * documentation.  The authors of MySQL hereby grant you an additional
+ * permission to link the program and your derivative works with the
+ * separately licensed software that they have either included with
+ * the program or referenced in the documentation.
+ *
+ * This program is distributed in the hope that it will be useful,  but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
+ * the GNU General Public License, version 2.0, for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+extern "C" {
+#include <com_err.h>
+}
+
+#include <krb5/krb5.h>
+
+namespace {
+
+class Context final {
+ public:
+  Context() = delete;
+
+  explicit Context(krb5_context context) : m_context(context) {}
+
+  Context(const Context &) = delete;
+  Context(Context &&) = delete;
+
+  Context &operator=(const Context &) = delete;
+  Context &operator=(Context &&) = delete;
+
+  ~Context() { krb5_free_context(m_context); }
+
+ private:
+  krb5_context m_context;
+};
+
+}  // namespace
+
+int main() {
+  krb5_error_code code;
+
+  const auto report_error = [&code](const char *msg) {
+    com_err("kdestroy", code, "%s", msg);
+    return 1;
+  };
+
+  krb5_context context;
+
+  if (code = krb5_init_context(&context); code) {
+    return report_error("Failed to initialize krb5 context");
+  }
+
+  Context deleter{context};
+  krb5_ccache cache = nullptr;
+
+  if (code = krb5_cc_default(context, &cache); code) {
+    return report_error("Failed to resolve default cache name");
+  }
+
+  if (code = krb5_cc_destroy(context, cache); code && KRB5_FCC_NOFILE != code) {
+    return report_error("Failed to destroy default cache");
+  }
+
+  return 0;
+}
diff --git a/unittest/modules/instance_cache_t.cc b/unittest/modules/instance_cache_t.cc
index 86a9d29298..8ebac466eb 100644
--- a/unittest/modules/instance_cache_t.cc
+++ b/unittest/modules/instance_cache_t.cc
@@ -1093,6 +1093,9 @@ TEST_F(Instance_cache_test, schema_collation) {
 }
 
 TEST_F(Instance_cache_test, table_metadata) {
+  const auto supports_secondary_engine =
+      m_session->get_server_version().numeric() >= 80013;
+
   {
     // setup
     m_session->execute("CREATE SCHEMA first;");
@@ -1112,6 +1115,14 @@ TEST_F(Instance_cache_test, table_metadata) {
         "ENGINE = BLACKHOLE "
         "COMMENT = 'important table'"
         ";");
+
+    if (supports_secondary_engine) {
+      m_session->execute(
+          "CREATE TABLE second.four (id INT) "
+          "ENGINE = InnoDB "
+          "secondary_ENGINE = 'raPID'"
+          ";");
+    }
   }
 
   {
@@ -1123,6 +1134,7 @@ TEST_F(Instance_cache_test, table_metadata) {
     {
       const auto &one = cache.schemas.at("first").tables.at("one");
       EXPECT_EQ("InnoDB", one.engine);
+      EXPECT_EQ("", one.secondary_engine);
       EXPECT_EQ("", one.comment);
       EXPECT_EQ("partitioned", one.create_options);
     }
@@ -1130,6 +1142,7 @@ TEST_F(Instance_cache_test, table_metadata) {
     {
       const auto &two = cache.schemas.at("first").tables.at("two");
       EXPECT_EQ("MyISAM", two.engine);
+      EXPECT_EQ("", two.secondary_engine);
       EXPECT_EQ("qwerty'asdfg", two.comment);
       EXPECT_EQ("", two.create_options);
     }
@@ -1137,9 +1150,19 @@ TEST_F(Instance_cache_test, table_metadata) {
     {
       const auto &three = cache.schemas.at("second").tables.at("three");
       EXPECT_EQ("BLACKHOLE", three.engine);
+      EXPECT_EQ("", three.secondary_engine);
       EXPECT_EQ("important table", three.comment);
       EXPECT_EQ("", three.create_options);
     }
+
+    if (supports_secondary_engine) {
+      const auto &four = cache.schemas.at("second").tables.at("four");
+      EXPECT_EQ("InnoDB", four.engine);
+      EXPECT_EQ("RAPID", four.secondary_engine);
+      EXPECT_EQ("", four.comment);
+      EXPECT_NE(std::string::npos,
+                four.create_options.find("SECONDARY_ENGINE=\"raPID\""));
+    }
   }
 }
 
diff --git a/unittest/mysql-secret-store-plaintext/plaintext_helper.cc b/unittest/mysql-secret-store-plaintext/plaintext_helper.cc
index 25d746be6a..aa0be294d7 100644
--- a/unittest/mysql-secret-store-plaintext/plaintext_helper.cc
+++ b/unittest/mysql-secret-store-plaintext/plaintext_helper.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -48,7 +48,7 @@ namespace {
 
 constexpr auto k_secret = "Secret";
 constexpr auto k_secret_type = "SecretType";
-constexpr auto k_server_url = "ServerURL";
+constexpr auto k_secret_id = "SecretID";
 
 std::string to_string(rapidjson::Document *doc) {
   rapidjson::StringBuffer buffer;
@@ -80,9 +80,12 @@ std::vector<common::Secret> load_file(const std::string &path) {
 
     if (doc.IsArray()) {
       for (const auto &s : doc.GetArray()) {
-        secrets.emplace_back(s[k_secret].GetString(),
-                             common::Secret_id{s[k_secret_type].GetString(),
-                                               s[k_server_url].GetString()});
+        secrets.emplace_back(
+            std::string{s[k_secret].GetString(), s[k_secret].GetStringLength()},
+            common::Secret_id{{s[k_secret_type].GetString(),
+                               s[k_secret_type].GetStringLength()},
+                              {s[k_secret_id].GetString(),
+                               s[k_secret_id].GetStringLength()}});
       }
     }
   }
@@ -106,8 +109,8 @@ void save_file(const std::string &path,
                 rapidjson::StringRef(s.id.secret_type.c_str(),
                                      s.id.secret_type.length()),
                 allocator);
-    v.AddMember(rapidjson::StringRef(k_server_url),
-                rapidjson::StringRef(s.id.url.c_str(), s.id.url.length()),
+    v.AddMember(rapidjson::StringRef(k_secret_id),
+                rapidjson::StringRef(s.id.id.c_str(), s.id.id.length()),
                 allocator);
     doc.PushBack(v, allocator);
   }
@@ -121,7 +124,7 @@ std::vector<common::Secret>::iterator find_secret(
     const common::Secret_id &id, std::vector<common::Secret> *secrets) {
   return std::find_if(
       secrets->begin(), secrets->end(), [&id](const common::Secret &s) {
-        return s.id.secret_type == id.secret_type && s.id.url == id.url;
+        return s.id.secret_type == id.secret_type && s.id.id == id.id;
       });
 }
 
@@ -172,11 +175,14 @@ void Plaintext_helper::erase(const common::Secret_id &id) {
   save_file(path, data);
 }
 
-void Plaintext_helper::list(std::vector<common::Secret_id> *secrets) {
+void Plaintext_helper::list(std::vector<common::Secret_id> *secrets,
+                            std::optional<std::string> secret_type) {
   auto data = load_file(get_file_name());
 
   for (auto &s : data) {
-    secrets->emplace_back(std::move(s.id));
+    if (!secret_type.has_value() || s.id.secret_type == *secret_type) {
+      secrets->emplace_back(std::move(s.id));
+    }
   }
 }
 
diff --git a/unittest/mysql-secret-store-plaintext/plaintext_helper.h b/unittest/mysql-secret-store-plaintext/plaintext_helper.h
index bed2d54b71..e3642bfb9a 100644
--- a/unittest/mysql-secret-store-plaintext/plaintext_helper.h
+++ b/unittest/mysql-secret-store-plaintext/plaintext_helper.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -47,7 +47,8 @@ class Plaintext_helper : public common::Helper {
 
   void erase(const common::Secret_id &) override;
 
-  void list(std::vector<common::Secret_id> *) override;
+  void list(std::vector<common::Secret_id> *,
+            std::optional<std::string>) override;
 
  private:
   std::string get_file_name() const;
diff --git a/unittest/mysql_secret_store_t.cc b/unittest/mysql_secret_store_t.cc
index 96fef96544..e8c9bfefa1 100644
--- a/unittest/mysql_secret_store_t.cc
+++ b/unittest/mysql_secret_store_t.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -26,10 +26,16 @@
 #include <rapidjson/document.h>
 
 #include <algorithm>
+#include <cstdio>
+#include <cstdlib>
 #include <iterator>
 #include <memory>
+#include <optional>
 #include <set>
 #include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
 #include <vector>
 
 #include "mysql-secret-store/include/mysql-secret-store/api.h"
@@ -38,11 +44,13 @@
 #include "mysqlshdk/libs/utils/process_launcher.h"
 #include "mysqlshdk/libs/utils/utils_file.h"
 #include "mysqlshdk/libs/utils/utils_general.h"
+#include "mysqlshdk/libs/utils/utils_json.h"
 #include "mysqlshdk/libs/utils/utils_path.h"
 #include "mysqlshdk/libs/utils/utils_string.h"
 #include "mysqlshdk/libs/utils/version.h"
 #include "unittest/gtest_clean.h"
 #include "unittest/test_utils/mocks/gmock_clean.h"
+#include "unittest/test_utils/shell_test_env.h"
 #include "unittest/test_utils/shell_test_wrapper.h"
 
 using mysql::secret_store::api::get_available_helpers;
@@ -71,11 +79,15 @@ ::testing::AssertionResult is_empty(const T &container) {
 }
 
 constexpr auto k_secret_type_password = "password";
+constexpr auto k_secret_type_generic = "generic";
 
 std::string to_string(Secret_type type) {
   switch (type) {
     case Secret_type::PASSWORD:
       return k_secret_type_password;
+
+    case Secret_type::GENERIC:
+      return k_secret_type_generic;
   }
 
   throw std::runtime_error{"Unknown secret type"};
@@ -88,26 +100,47 @@ class Helper_tester {
  public:
   virtual ~Helper_tester() = default;
 
+  virtual bool uses_wrapper() const { return false; }
+  virtual void use_wrapper(Shell_test_wrapper *) {}
   virtual void select_helper(const std::string &name) = 0;
-  virtual void clear_store() = 0;
+  virtual void clear_store(std::optional<Secret_type> type = {}) = 0;
   virtual bool has_get() = 0;
 
-  virtual bool store(Secret_type type, const std::string &url,
+  virtual bool store(Secret_type type, const std::string &id,
                      const std::string &secret) = 0;
-  virtual bool get(Secret_type type, const std::string &url,
+  virtual bool get(Secret_type type, const std::string &id,
                    std::string *secret) = 0;
-  virtual bool erase(Secret_type type, const std::string &url) = 0;
+  virtual bool erase(Secret_type type, const std::string &id) = 0;
 
-  virtual bool list(std::vector<Secret_spec> *specs) = 0;
+  virtual bool list(std::vector<Secret_spec> *specs,
+                    std::optional<Secret_type> type = {}) = 0;
 
   virtual void expect_error(const std::string &msg) = 0;
   virtual void expect_no_error() = 0;
 
-  virtual void expect_contains(Secret_type type, const std::string &url,
+  virtual void expect_contains(Secret_type type, const std::string &id,
                                const std::string &secret) = 0;
-  virtual void expect_not_contain(Secret_type type, const std::string &url) = 0;
+  virtual void expect_not_contains(Secret_type type, const std::string &id) = 0;
 };
 
+template <typename T>
+struct Helpers {
+  static std::set<std::string> list;
+
+ private:
+  static std::set<std::string> list_helpers() {
+    SCOPED_TRACE("Helpers::list_helpers()");
+    auto helpers = T::list_helpers();
+    std::set<std::string> ret{helpers.begin(), helpers.end()};
+    EXPECT_TRUE(helpers.size() == ret.size())
+        << "List of helpers contains duplicate values";
+    return ret;
+  }
+};
+
+template <typename T>
+std::set<std::string> Helpers<T>::list = Helpers<T>::list_helpers();
+
 class Mysql_secret_store_api_tester : public Helper_tester {
  public:
   static std::vector<std::string> list_helpers() {
@@ -137,14 +170,16 @@ class Mysql_secret_store_api_tester : public Helper_tester {
     ASSERT_NE(nullptr, m_helper);
   }
 
-  void clear_store() override {
+  void clear_store(std::optional<Secret_type> type = {}) override {
     SCOPED_TRACE("Mysql_secret_store_api_tester::clear_store()");
 
     std::vector<Secret_spec> specs;
     ASSERT_NE(nullptr, m_helper);
-    EXPECT_TRUE(m_helper->list(&specs));
+    EXPECT_TRUE(m_helper->list(&specs, type));
     expect_no_error();
+
     for (const auto &spec : specs) {
+      SCOPED_TRACE("erasing: " + spec.id);
       EXPECT_TRUE(m_helper->erase(spec));
       expect_no_error();
     }
@@ -152,26 +187,27 @@ class Mysql_secret_store_api_tester : public Helper_tester {
 
   bool has_get() override { return true; }
 
-  bool store(Secret_type type, const std::string &url,
+  bool store(Secret_type type, const std::string &id,
              const std::string &secret) override {
     SCOPED_TRACE_TYPE("Mysql_secret_store_api_tester::store()");
-    return m_helper->store({type, url}, secret);
+    return m_helper->store({type, id}, secret);
   }
 
-  bool get(Secret_type type, const std::string &url,
+  bool get(Secret_type type, const std::string &id,
            std::string *secret) override {
     SCOPED_TRACE_TYPE("Mysql_secret_store_api_tester::get()");
-    return m_helper->get({type, url}, secret);
+    return m_helper->get({type, id}, secret);
   }
 
-  bool erase(Secret_type type, const std::string &url) override {
+  bool erase(Secret_type type, const std::string &id) override {
     SCOPED_TRACE_TYPE("Mysql_secret_store_api_tester::erase()");
-    return m_helper->erase({type, url});
+    return m_helper->erase({type, id});
   }
 
-  bool list(std::vector<Secret_spec> *specs) override {
+  bool list(std::vector<Secret_spec> *specs,
+            std::optional<Secret_type> type = {}) override {
     SCOPED_TRACE("Mysql_secret_store_api_tester::list()");
-    return m_helper->list(specs);
+    return m_helper->list(specs, type);
   }
 
   void expect_error(const std::string &msg) override {
@@ -184,19 +220,19 @@ class Mysql_secret_store_api_tester : public Helper_tester {
     EXPECT_EQ("", m_helper->get_last_error());
   }
 
-  void expect_contains(Secret_type type, const std::string &url,
+  void expect_contains(Secret_type type, const std::string &id,
                        const std::string &secret) override {
     SCOPED_TRACE_TYPE("Mysql_secret_store_api_tester::expect_contains()");
     std::string stored_secret;
-    EXPECT_TRUE(get(type, url, &stored_secret));
+    EXPECT_TRUE(get(type, id, &stored_secret));
     expect_no_error();
     EXPECT_EQ(secret, stored_secret);
   }
 
-  void expect_not_contain(Secret_type type, const std::string &url) override {
-    SCOPED_TRACE_TYPE("Mysql_secret_store_api_tester::expect_not_contain()");
+  void expect_not_contains(Secret_type type, const std::string &id) override {
+    SCOPED_TRACE_TYPE("Mysql_secret_store_api_tester::expect_not_contains()");
     std::string stored_secret;
-    EXPECT_FALSE(get(type, url, &stored_secret));
+    EXPECT_FALSE(get(type, id, &stored_secret));
   }
 
   std::string name() const { return m_helper->name().get(); }
@@ -207,140 +243,158 @@ class Mysql_secret_store_api_tester : public Helper_tester {
 
 class Shell_api_tester : public Helper_tester {
  public:
-  Shell_api_tester() {}
-
   static std::vector<std::string> list_helpers() {
     // this is invoked too early to call execute(), use library function
     return Mysql_secret_store_api_tester::call_get_available_helpers();
   }
 
-  std::vector<std::string> call_list_helpers() {
-    execute("print(shell.listCredentialHelpers());");
+  bool uses_wrapper() const override { return true; }
 
-    return parse_output();
+  void use_wrapper(Shell_test_wrapper *wrapper) override {
+    m_wrapper = wrapper;
+    m_wrapper->execute("\\js");
   }
 
   void select_helper(const std::string &name) override {
     SCOPED_TRACE("Shell_api_tester::select_helper()");
 
-    m_wrapper.reset(new Shell_test_wrapper{false});
-    m_wrapper->execute("\\js");
-
     execute("shell.options[\"credentialStore.helper\"] = " +
             shcore::quote_string(name, '\"') + ";");
 
-    ASSERT_EQ("", m_wrapper->get_output_handler().std_err);
+    ASSERT_EQ("", output_handler().std_err);
   }
 
-  void clear_store() override {
+  void clear_store(std::optional<Secret_type> type = {}) override {
     SCOPED_TRACE("Shell_api_tester::clear_store()");
 
+    if (type.has_value()) {
+      EXPECT_EQ(m_config.type, *type);
+    }
+
     expect_delete_all_credentials();
   }
 
-  bool has_get() override { return false; }
+  bool has_get() override { return m_config.get; }
 
-  bool store(Secret_type type, const std::string &url,
+  bool store(Secret_type type, const std::string &id,
              const std::string &secret) override {
     SCOPED_TRACE_TYPE("Shell_api_tester::store()");
 
-    EXPECT_EQ(Secret_type::PASSWORD, type);
+    EXPECT_EQ(m_config.type, type);
+    execute(shcore::str_format("shell.%s(%s, %s);", m_config.store,
+                               shcore::quote_string(id, '\"').c_str(),
+                               shcore::quote_string(secret, '\"').c_str()));
 
-#ifdef HAVE_JS
-    execute("shell.storeCredential(" + shcore::quote_string(url, '\"') + ", " +
-            shcore::quote_string(secret, '\"') + ");");
-#else
-    execute("shell.store_credential(" + shcore::quote_string(url, '\"') + ", " +
-            shcore::quote_string(secret, '\"') + ");");
-#endif
-    return m_wrapper->get_output_handler().std_err.empty();
+    return output_handler().std_err.empty();
   }
 
-  bool get(Secret_type, const std::string &, std::string *) override {
-    ADD_FAILURE() << "This should not be called";
-    return false;
+  bool get(Secret_type type, const std::string &id,
+           std::string *secret) override {
+    SCOPED_TRACE_TYPE("Shell_api_tester::get()");
+
+    if (has_get()) {
+      EXPECT_EQ(m_config.type, type);
+      execute(shcore::str_format("print(shell.%s(%s));", m_config.get,
+                                 shcore::quote_string(id, '\"').c_str()));
+
+      *secret = output_handler().std_out;
+
+      return output_handler().std_err.empty();
+    } else {
+      return false;
+    }
   }
 
-  bool erase(Secret_type type, const std::string &url) override {
+  bool erase(Secret_type type, const std::string &id) override {
     SCOPED_TRACE_TYPE("Shell_api_tester::erase()");
 
-    EXPECT_EQ(Secret_type::PASSWORD, type);
+    EXPECT_EQ(m_config.type, type);
+    execute(shcore::str_format("shell.%s(%s);", m_config.erase,
+                               shcore::quote_string(id, '\"').c_str()));
 
-#ifdef HAVE_JS
-    execute("shell.deleteCredential(" + shcore::quote_string(url, '\"') + ");");
-#else
-    execute("shell.delete_credential(" + shcore::quote_string(url, '\"') +
-            ");");
-#endif
-
-    return m_wrapper->get_output_handler().std_err.empty();
+    return output_handler().std_err.empty();
   }
 
-  bool list(std::vector<Secret_spec> *specs) override {
+  bool list(std::vector<Secret_spec> *specs,
+            std::optional<Secret_type> type = {}) override {
     SCOPED_TRACE("Shell_api_tester::list()");
 
-#ifdef HAVE_JS
-    execute("print(shell.listCredentials());");
-#else
-    execute("print(shell.list_credentials());");
-#endif
+    if (type.has_value()) {
+      EXPECT_EQ(m_config.type, *type);
+    }
+
+    execute(shcore::str_format("print(shell.%s());", m_config.list));
 
-    for (const auto &url : parse_output()) {
-      specs->emplace_back(Secret_spec{Secret_type::PASSWORD, url});
+    for (const auto &id : parse_array_output()) {
+      specs->emplace_back(Secret_spec{m_config.type, id});
     }
 
-    return m_wrapper->get_output_handler().std_err.empty();
+    return output_handler().std_err.empty();
   }
 
   void expect_error(const std::string &msg) override {
     SCOPED_TRACE("Shell_api_tester::expect_error()");
-    m_wrapper->get_output_handler().validate_stderr_content(msg, true);
+    output_handler().validate_stderr_content(msg, true);
   }
 
   void expect_no_error() override {
     SCOPED_TRACE("Shell_api_tester::expect_no_error()");
-    EXPECT_EQ("", m_wrapper->get_output_handler().std_err);
+    EXPECT_EQ("", output_handler().std_err);
   }
 
-  void expect_contains(Secret_type type, const std::string &url,
+  void expect_contains(Secret_type type, const std::string &id,
                        const std::string &) override {
     SCOPED_TRACE_TYPE("Shell_api_tester::expect_contains()");
 
-    EXPECT_EQ(Secret_type::PASSWORD, type);
-    EXPECT_TRUE(has_url(url));
+    EXPECT_EQ(m_config.type, type);
+    EXPECT_TRUE(contains_id(id));
   }
 
-  void expect_not_contain(Secret_type type, const std::string &url) override {
-    SCOPED_TRACE_TYPE("Shell_api_tester::expect_not_contain()");
+  void expect_not_contains(Secret_type type, const std::string &id) override {
+    SCOPED_TRACE_TYPE("Shell_api_tester::expect_not_contains()");
 
-    EXPECT_EQ(Secret_type::PASSWORD, type);
-    EXPECT_FALSE(has_url(url));
+    EXPECT_EQ(m_config.type, type);
+    EXPECT_FALSE(contains_id(id));
   }
 
   void expect_delete_all_credentials() {
     SCOPED_TRACE("Shell_api_tester::expect_delete_all_credentials()");
 
-#ifdef HAVE_JS
-    execute("shell.deleteAllCredentials();");
-#else
-    execute("shell.delete_all_credentials();");
-#endif
+    execute(shcore::str_format("shell.%s();", m_config.clear));
 
-    EXPECT_EQ("", m_wrapper->get_output_handler().std_err);
+    EXPECT_EQ("", output_handler().std_err);
   }
 
- private:
+ protected:
+  struct Config {
+    Secret_type type;
+    const char *clear;
+    const char *erase;
+    const char *list;
+    const char *get;
+    const char *store;
+  };
+
+  explicit Shell_api_tester(Config &&config) : m_config(std::move(config)) {}
+
   void execute(const std::string &code) {
-    m_wrapper->get_output_handler().wipe_all();
+    output_handler().wipe_all();
     m_wrapper->execute(code);
   }
 
-  std::vector<std::string> parse_output() {
+  inline Shell_test_output_handler &output_handler() {
+    return m_wrapper->get_output_handler();
+  }
+
+ private:
+  virtual std::string normalize_id(const std::string &id) const = 0;
+
+  std::vector<std::string> parse_array_output() {
     std::vector<std::string> output;
 
     rapidjson::Document doc;
 
-    doc.Parse(m_wrapper->get_output_handler().std_out.c_str());
+    doc.Parse(output_handler().std_out.c_str());
 
     if (!doc.HasParseError() && doc.IsArray()) {
       for (const auto &s : doc.GetArray()) {
@@ -351,29 +405,180 @@ class Shell_api_tester : public Helper_tester {
     return output;
   }
 
-  bool has_url(const std::string &url) {
-    SCOPED_TRACE("Shell_api_tester::has_url()");
+  bool contains_id(const std::string &id) {
+    SCOPED_TRACE("Shell_api_tester::contains_id()");
 
     std::vector<Secret_spec> specs;
 
     EXPECT_TRUE(list(&specs));
     expect_no_error();
+
+    return specs.end() !=
+           std::find(specs.begin(), specs.end(),
+                     Secret_spec{m_config.type, normalize_id(id)});
+  }
+
+  Config m_config;
+  Shell_test_wrapper *m_wrapper = nullptr;
+};
+
+class Shell_credential_api_tester : public Shell_api_tester {
+ public:
+  Shell_credential_api_tester()
+      : Shell_api_tester({
+            Secret_type::PASSWORD,
+            "deleteAllCredentials",
+            "deleteCredential",
+            "listCredentials",
+            nullptr,
+            "storeCredential",
+        }) {}
+
+ private:
+  std::string normalize_id(const std::string &id) const override {
     mysqlshdk::db::uri::Generic_uri copts;
     mysqlshdk::db::uri::Uri_parser parser(mysqlshdk::db::uri::Type::Generic);
-    parser.parse(url, &copts);
+    parser.parse(id, &copts);
 
     auto tokens = mysqlshdk::db::uri::formats::user_transport();
+
     if (copts.has_value(mysqlshdk::db::kScheme) &&
         (copts.get(mysqlshdk::db::kScheme) == "ssh" ||
-         copts.get(mysqlshdk::db::kScheme) == "file"))
+         copts.get(mysqlshdk::db::kScheme) == "file")) {
       tokens.set(mysqlshdk::db::uri::Tokens::Scheme);
-    auto normalized = copts.as_uri(tokens);
-    return specs.end() !=
-           std::find(specs.begin(), specs.end(),
-                     Secret_spec{Secret_type::PASSWORD, normalized});
+    }
+
+    return copts.as_uri(tokens);
   }
+};
 
-  std::unique_ptr<Shell_test_wrapper> m_wrapper;
+class Shell_secret_api_tester : public Shell_api_tester {
+ public:
+  Shell_secret_api_tester()
+      : Shell_api_tester({
+            Secret_type::GENERIC,
+            "deleteAllSecrets",
+            "deleteSecret",
+            "listSecrets",
+            "readSecret",
+            "storeSecret",
+        }) {}
+
+ private:
+  std::string normalize_id(const std::string &id) const override { return id; }
+};
+
+class Shell_all_api_tester : public Helper_tester {
+ public:
+  Shell_all_api_tester() {
+    m_testers[Secret_type::PASSWORD] =
+        std::make_unique<Shell_credential_api_tester>();
+    m_testers[Secret_type::GENERIC] =
+        std::make_unique<Shell_secret_api_tester>();
+  }
+
+  static std::vector<std::string> list_helpers() {
+    SCOPED_TRACE("Shell_all_api_tester::list_helpers()");
+    return Shell_api_tester::list_helpers();
+  }
+
+  bool uses_wrapper() const override { return true; }
+
+  void use_wrapper(Shell_test_wrapper *wrapper) override {
+    for (const auto &tester : m_testers) {
+      tester.second->use_wrapper(wrapper);
+    }
+  }
+
+  void select_helper(const std::string &name) override {
+    SCOPED_TRACE("Shell_all_api_tester::select_helper()");
+
+    for (const auto &tester : m_testers) {
+      tester.second->select_helper(name);
+    }
+  }
+
+  void clear_store(std::optional<Secret_type> type = {}) override {
+    SCOPED_TRACE("Shell_all_api_tester::clear_store()");
+
+    for (const auto &tester : m_testers) {
+      if (!type.has_value() || tester.first == *type) {
+        tester.second->clear_store(type);
+      }
+    }
+  }
+
+  bool has_get() override {
+    for (const auto &tester : m_testers) {
+      if (!tester.second->has_get()) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  bool store(Secret_type type, const std::string &id,
+             const std::string &secret) override {
+    SCOPED_TRACE_TYPE("Shell_all_api_tester::store()");
+    return tester(type)->store(type, id, secret);
+  }
+
+  bool get(Secret_type type, const std::string &id,
+           std::string *secret) override {
+    SCOPED_TRACE_TYPE("Shell_all_api_tester::get()");
+    return tester(type)->get(type, id, secret);
+  }
+
+  bool erase(Secret_type type, const std::string &id) override {
+    SCOPED_TRACE_TYPE("Shell_all_api_tester::erase()");
+    return tester(type)->erase(type, id);
+  }
+
+  bool list(std::vector<Secret_spec> *specs,
+            std::optional<Secret_type> type = {}) override {
+    SCOPED_TRACE("Shell_all_api_tester::list()");
+
+    for (const auto &tester : m_testers) {
+      if (!type.has_value() || tester.first == *type) {
+        if (!tester.second->list(specs, type)) {
+          return false;
+        }
+      }
+    }
+
+    return true;
+  }
+
+  void expect_error(const std::string &msg) override {
+    SCOPED_TRACE("Shell_all_api_tester::expect_error()");
+    // it doesn't matter which tester we call here
+    m_testers.begin()->second->expect_error(msg);
+  }
+
+  void expect_no_error() override {
+    SCOPED_TRACE("Shell_all_api_tester::expect_no_error()");
+    // it doesn't matter which tester we call here
+    m_testers.begin()->second->expect_no_error();
+  }
+
+  void expect_contains(Secret_type type, const std::string &id,
+                       const std::string &secret) override {
+    SCOPED_TRACE_TYPE("Shell_all_api_tester::expect_contains()");
+    tester(type)->expect_contains(type, id, secret);
+  }
+
+  void expect_not_contains(Secret_type type, const std::string &id) override {
+    SCOPED_TRACE_TYPE("Shell_all_api_tester::expect_not_contains()");
+    tester(type)->expect_not_contains(type, id);
+  }
+
+  inline Shell_api_tester *tester(Secret_type type) const {
+    return m_testers.at(type).get();
+  }
+
+ private:
+  std::unordered_map<Secret_type, std::unique_ptr<Shell_api_tester>> m_testers;
 };
 
 class Helper_invoker {
@@ -392,7 +597,9 @@ class Helper_invoker {
     return invoke("erase", input, output);
   }
 
-  bool list(std::string *output) const { return invoke("list", {}, output); }
+  bool list(std::string *output, const std::string &input = {}) const {
+    return invoke("list", input, output);
+  }
 
   bool version(std::string *output) const {
     return invoke("version", {}, output);
@@ -408,9 +615,10 @@ class Helper_invoker {
 
       if (!input.empty()) {
         app.write(input.c_str(), input.length());
-        app.finish_writing();
       }
 
+      app.finish_writing();
+
       *output = shcore::str_strip(app.read_all());
 
       return app.wait() == 0;
@@ -446,14 +654,20 @@ class Helper_executable_tester : public Helper_tester {
     ASSERT_NE(nullptr, m_invoker);
   }
 
-  void clear_store() override {
+  void clear_store(std::optional<Secret_type> type = {}) override {
     SCOPED_TRACE("Helper_executable_tester::clear_store()");
 
     ASSERT_NE(nullptr, m_invoker);
 
+    std::string input;
+
+    if (type.has_value()) {
+      input = to_json(*type);
+    }
+
     std::string output;
 
-    if (!m_invoker->list(&output)) {
+    if (!m_invoker->list(&output, input)) {
       ::testing::AssertionFailure() << output;
       return;
     }
@@ -474,9 +688,9 @@ class Helper_executable_tester : public Helper_tester {
 
     for (const auto &s : doc.GetArray()) {
       output.clear();
-      if (!m_invoker->erase(to_json(s[k_secret_type].GetString(),
-                                    s[k_server_url].GetString()),
-                            &output)) {
+      if (!m_invoker->erase(
+              to_json(s[k_secret_type].GetString(), s[k_secret_id].GetString()),
+              &output)) {
         ::testing::AssertionFailure() << output << "\n";
       }
     }
@@ -484,12 +698,12 @@ class Helper_executable_tester : public Helper_tester {
 
   bool has_get() override { return true; }
 
-  bool store(Secret_type type, const std::string &url,
+  bool store(Secret_type type, const std::string &id,
              const std::string &secret) override {
     SCOPED_TRACE_TYPE("Helper_executable_tester::store()");
 
     std::string output;
-    bool ret = m_invoker->store(to_json(type, url, secret), &output);
+    bool ret = m_invoker->store(to_json(type, id, secret), &output);
 
     if (ret) {
       clear_last_error();
@@ -500,19 +714,19 @@ class Helper_executable_tester : public Helper_tester {
     return ret;
   }
 
-  bool get(Secret_type type, const std::string &url,
+  bool get(Secret_type type, const std::string &id,
            std::string *secret) override {
     SCOPED_TRACE_TYPE("Helper_executable_tester::get()");
 
     std::string output;
-    bool ret = m_invoker->get(to_json(type, url), &output);
+    bool ret = m_invoker->get(to_json(type, id), &output);
 
     if (ret) {
       clear_last_error();
       Secret_spec spec;
       ret = to_secret(output, &spec, secret);
 
-      if (spec.type != type || spec.url != url) {
+      if (spec.type != type || spec.id != id) {
         ret = false;
         set_last_error("Helper returned mismatched secret");
       }
@@ -523,11 +737,11 @@ class Helper_executable_tester : public Helper_tester {
     return ret;
   }
 
-  bool erase(Secret_type type, const std::string &url) override {
+  bool erase(Secret_type type, const std::string &id) override {
     SCOPED_TRACE_TYPE("Helper_executable_tester::erase()");
 
     std::string output;
-    bool ret = m_invoker->erase(to_json(type, url), &output);
+    bool ret = m_invoker->erase(to_json(type, id), &output);
 
     if (ret) {
       clear_last_error();
@@ -538,11 +752,18 @@ class Helper_executable_tester : public Helper_tester {
     return ret;
   }
 
-  bool list(std::vector<Secret_spec> *specs) override {
+  bool list(std::vector<Secret_spec> *specs,
+            std::optional<Secret_type> type = {}) override {
     SCOPED_TRACE("Helper_executable_tester::list()");
 
+    std::string input;
+
+    if (type.has_value()) {
+      input = to_json(*type);
+    }
+
     std::string output;
-    bool ret = m_invoker->list(&output);
+    bool ret = m_invoker->list(&output, input);
 
     if (ret) {
       clear_last_error();
@@ -564,23 +785,92 @@ class Helper_executable_tester : public Helper_tester {
     EXPECT_EQ("", m_last_error);
   }
 
-  void expect_contains(Secret_type type, const std::string &url,
+  void expect_contains(Secret_type type, const std::string &id,
                        const std::string &secret) override {
     SCOPED_TRACE_TYPE("Helper_executable_tester::expect_contains()");
     std::string stored_secret;
-    EXPECT_TRUE(get(type, url, &stored_secret));
+    EXPECT_TRUE(get(type, id, &stored_secret));
     expect_no_error();
     EXPECT_EQ(secret, stored_secret);
   }
 
-  void expect_not_contain(Secret_type type, const std::string &url) override {
-    SCOPED_TRACE_TYPE("Helper_executable_tester::expect_not_contain()");
+  void expect_not_contains(Secret_type type, const std::string &id) override {
+    SCOPED_TRACE_TYPE("Helper_executable_tester::expect_not_contains()");
     std::string stored_secret;
-    EXPECT_FALSE(get(type, url, &stored_secret));
+    EXPECT_FALSE(get(type, id, &stored_secret));
   }
 
   Helper_invoker &get_invoker() { return *m_invoker; }
 
+  static std::string to_json(Secret_type type, const std::string &id,
+                             const std::string &secret) {
+    return to_json(to_string(type), id, secret);
+  }
+
+  static std::string to_json(const std::string &type, const std::string &id,
+                             const std::string &secret) {
+    shcore::JSON_dumper json;
+
+    json.start_object();
+    to_json(type, id, &json);
+    json.append(k_secret, secret);
+    json.end_object();
+
+    return json.str();
+  }
+
+  static std::string to_json(Secret_type type, const std::string &id) {
+    return to_json(to_string(type), id);
+  }
+
+  static std::string to_json(const std::string &type, const std::string &id) {
+    shcore::JSON_dumper json;
+
+    json.start_object();
+    to_json(type, id, &json);
+    json.end_object();
+
+    return json.str();
+  }
+
+  static std::string to_json(Secret_type type) {
+    return to_json(to_string(type));
+  }
+
+  static std::string to_json(const std::string &type) {
+    shcore::JSON_dumper json;
+
+    json.start_object();
+    to_json(type, &json);
+    json.end_object();
+
+    return json.str();
+  }
+
+  bool to_secret(const std::string &json, Secret_spec *spec,
+                 std::string *secret) {
+    rapidjson::Document doc;
+
+    doc.Parse(json.c_str());
+
+    if (doc.HasParseError()) {
+      set_last_error("Failed to parse JSON");
+      return false;
+    }
+
+    if (spec && !to_spec(doc, spec)) {
+      return false;
+    }
+
+    if (!validate_object(doc, {k_secret})) {
+      return false;
+    }
+
+    secret->assign(doc[k_secret].GetString(), doc[k_secret].GetStringLength());
+
+    return true;
+  }
+
  private:
   static std::string get_helper_name(const std::string &path) {
     std::string name =
@@ -611,22 +901,14 @@ class Helper_executable_tester : public Helper_tester {
     return helpers;
   }
 
-  static std::string to_json(Secret_type type, const std::string &url,
-                             const std::string &secret) {
-    auto json = to_json(type, url);
-    json.pop_back();
-    return json + ",\"" + k_secret +
-           "\":" + shcore::quote_string(secret, '\"') + "}";
-  }
-
-  static std::string to_json(Secret_type type, const std::string &url) {
-    return to_json(to_string(type), url);
+  static void to_json(const std::string &type, const std::string &id,
+                      shcore::JSON_dumper *json) {
+    to_json(type, json);
+    json->append(k_secret_id, id);
   }
 
-  static std::string to_json(const std::string &type, const std::string &url) {
-    return std::string{"{\""} + k_server_url +
-           "\":" + shcore::quote_string(url, '\"') + ",\"" + k_secret_type +
-           "\":" + shcore::quote_string(type, '\"') + "}";
+  static void to_json(const std::string &type, shcore::JSON_dumper *json) {
+    json->append(k_secret_type, type);
   }
 
   bool validate_object(const rapidjson::Value &object,
@@ -652,6 +934,8 @@ class Helper_executable_tester : public Helper_tester {
   bool to_secret_type(const std::string &type, Secret_type *output_type) {
     if (type == k_secret_type_password) {
       *output_type = Secret_type::PASSWORD;
+    } else if (type == k_secret_type_generic) {
+      *output_type = Secret_type::GENERIC;
     } else {
       set_last_error("Unknown secret type: \"" + type + "\"");
       return false;
@@ -661,7 +945,7 @@ class Helper_executable_tester : public Helper_tester {
   }
 
   bool to_spec(const rapidjson::Value &object, Secret_spec *spec) {
-    if (!validate_object(object, {k_secret_type, k_server_url})) {
+    if (!validate_object(object, {k_secret_type, k_secret_id})) {
       return false;
     }
 
@@ -669,31 +953,7 @@ class Helper_executable_tester : public Helper_tester {
       return false;
     }
 
-    spec->url = object[k_server_url].GetString();
-
-    return true;
-  }
-
-  bool to_secret(const std::string &json, Secret_spec *spec,
-                 std::string *secret) {
-    rapidjson::Document doc;
-
-    doc.Parse(json.c_str());
-
-    if (doc.HasParseError()) {
-      set_last_error("Failed to parse JSON");
-      return false;
-    }
-
-    if (!to_spec(doc, spec)) {
-      return false;
-    }
-
-    if (!validate_object(doc, {k_secret})) {
-      return false;
-    }
-
-    *secret = doc[k_secret].GetString();
+    spec->id = object[k_secret_id].GetString();
 
     return true;
   }
@@ -730,76 +990,106 @@ class Helper_executable_tester : public Helper_tester {
 
   void clear_last_error() const { m_last_error.clear(); }
 
-  static const char k_exe_prefix[];
-  static const char k_secret[];
-  static const char k_secret_type[];
-  static const char k_server_url[];
+  static constexpr auto k_exe_prefix = "mysql-secret-store-";
+  static constexpr auto k_secret = "Secret";
+  static constexpr auto k_secret_type = "SecretType";
+  static constexpr auto k_secret_id = "SecretID";
 
   std::unique_ptr<Helper_invoker> m_invoker;
 
   mutable std::string m_last_error;
 };
 
-const char Helper_executable_tester::k_exe_prefix[] = "mysql-secret-store-";
-const char Helper_executable_tester::k_secret[] = "Secret";
-const char Helper_executable_tester::k_secret_type[] = "SecretType";
-const char Helper_executable_tester::k_server_url[] = "ServerURL";
-
 template <typename T>
 class Parametrized_helper_test : public ::testing::TestWithParam<std::string> {
  public:
-  static std::set<std::string> list_helpers() {
-    SCOPED_TRACE("Parametrized_helper_test::list_helpers()");
-    auto helpers = T::list_helpers();
-    std::set<std::string> ret{helpers.begin(), helpers.end()};
-    EXPECT_TRUE(helpers.size() == ret.size())
-        << "List of helpers contains duplicate values";
-    return ret;
-  }
+  using Helper_t = T;
 
  protected:
   void SetUp() override {
-    tester.select_helper(GetParam());
+    if (Shell_test_env::get_target_server_version() <
+        mysqlshdk::utils::k_shell_version) {
+      // this is needed to prevent gtest from invoking the body of a test
+      GTEST_SKIP();
+      SKIP_TEST("This test is only executed once, with latest server");
+      return;
+    }
+
+    if (tester.uses_wrapper()) {
+      m_wrapper = std::make_unique<Shell_test_wrapper>(false);
+      tester.use_wrapper(m_wrapper.get());
+    }
+
+    tester.select_helper(helper_name());
+
+    if (::getenv("TEST_DEBUG")) {
+      mysql::secret_store::api::set_logger([](std::string_view msg) {
+        fprintf(stderr, "%.*s\n", static_cast<int>(msg.size()), msg.data());
+      });
+    }
+
+    clear_store();
+  }
+
+  void TearDown() override {
+    if (this->IsSkipped()) {
+      return;
+    }
+
     clear_store();
+
+    if (::getenv("TEST_DEBUG")) {
+      mysql::secret_store::api::set_logger({});
+    }
+
+    m_wrapper.reset();
   }
 
-  void TearDown() override { clear_store(); }
+  const std::string &helper_name() const { return GetParam(); }
+
+  bool is_login_path() const { return "login-path" == helper_name(); }
+
+  bool is_keychain() const { return "keychain" == helper_name(); }
 
-  bool store(const std::string &url, const std::string &secret) {
-    return tester.store(Secret_type::PASSWORD, url, secret);
+  bool is_secret_service() const { return "secret-service" == helper_name(); }
+
+  bool store(Secret_type type, const std::string &id,
+             const std::string &secret) {
+    return tester.store(type, id, secret);
   }
 
-  bool get(const std::string &url, std::string *secret) {
-    return tester.get(Secret_type::PASSWORD, url, secret);
+  bool get(Secret_type type, const std::string &id, std::string *secret) {
+    return tester.get(type, id, secret);
   }
 
-  bool erase(const std::string &url) {
-    return tester.erase(Secret_type::PASSWORD, url);
+  bool erase(Secret_type type, const std::string &id) {
+    return tester.erase(type, id);
   }
 
-  void expect_list(const std::set<std::string> &urls) {
+  void expect_list(Secret_type type, const std::set<std::string> &ids) {
     SCOPED_TRACE("Parametrized_helper_test::expect_list()");
     std::vector<Secret_spec> specs;
-    EXPECT_TRUE(tester.list(&specs));
+    EXPECT_TRUE(tester.list(&specs, type));
     expect_no_error();
 
-    std::set<std::string> list_urls;
+    std::set<std::string> list_ids;
 
     for (const auto &s : specs) {
-      SCOPED_TRACE(s.url + " -> " + to_string(s.type));
-      EXPECT_EQ(Secret_type::PASSWORD, s.type);
-      list_urls.emplace(s.url);
+      SCOPED_TRACE(s.id + " -> " + to_string(s.type));
+      EXPECT_EQ(type, s.type);
+      list_ids.emplace(s.id);
     }
 
-    EXPECT_EQ(urls, list_urls);
+    EXPECT_EQ(ids, list_ids);
   }
 
-  void expect_contains(const std::string &url, const std::string &secret) {
-    tester.expect_contains(Secret_type::PASSWORD, url, secret);
+  void expect_contains(Secret_type type, const std::string &id,
+                       const std::string &secret) {
+    tester.expect_contains(type, id, secret);
   }
 
-  void expect_not_contain(const std::string &url) {
-    tester.expect_not_contain(Secret_type::PASSWORD, url);
+  void expect_not_contains(Secret_type type, const std::string &id) {
+    tester.expect_not_contains(type, id);
   }
 
   void expect_error(const std::string &msg) { tester.expect_error(msg); }
@@ -809,18 +1099,49 @@ class Parametrized_helper_test : public ::testing::TestWithParam<std::string> {
   void clear_store() { tester.clear_store(); }
 
   T tester;
+
+ private:
+  std::unique_ptr<Shell_test_wrapper> m_wrapper;
+};
+
+template <typename T, Secret_type Type>
+class Parametrized_helper_test_with_type : public Parametrized_helper_test<T> {
+ protected:
+  bool store(const std::string &id, const std::string &secret) {
+    return Parametrized_helper_test<T>::store(Type, id, secret);
+  }
+
+  bool get(const std::string &id, std::string *secret) {
+    return Parametrized_helper_test<T>::get(Type, id, secret);
+  }
+
+  bool erase(const std::string &id) {
+    return Parametrized_helper_test<T>::erase(Type, id);
+  }
+
+  void expect_list(const std::set<std::string> &ids) {
+    Parametrized_helper_test<T>::expect_list(Type, ids);
+  }
+
+  void expect_contains(const std::string &id, const std::string &secret) {
+    Parametrized_helper_test<T>::expect_contains(Type, id, secret);
+  }
+
+  void expect_not_contains(const std::string &id) {
+    Parametrized_helper_test<T>::expect_not_contains(Type, id);
+  }
 };
 
 class Config_editor_invoker {
  public:
-  void store(const std::string &name, const std::string &url,
+  void store(const std::string &name, const std::string &id,
              const std::string &secret) {
     std::vector<std::string> args = {"set", "--skip-warn", "--password"};
     args.emplace_back("--login-path=" + name);
 
     mysqlshdk::db::uri::Generic_uri options;
     mysqlshdk::db::uri::Uri_parser parser(mysqlshdk::db::uri::Type::Generic);
-    parser.parse(url, &options);
+    parser.parse(id, &options);
 
     if (options.has_value(mysqlshdk::db::kUser)) {
       args.emplace_back("--user=" + options.get(mysqlshdk::db::kUser));
@@ -893,9 +1214,8 @@ void verify_available_helpers(const std::set<std::string> &helpers) {
 #else  // ! _WIN32
 #ifdef __APPLE__
   expected.emplace("keychain");
-#else   // ! __APPLE__
-  expected.emplace("login-path");
 #endif  // ! __APPLE__
+  expected.emplace("login-path");
 #endif  // ! _WIN32
 
   std::set<std::string> missing;
@@ -914,7 +1234,7 @@ template <typename T>
 void test_available_helpers() {
   SCOPED_TRACE("test_available_helpers()");
 
-  verify_available_helpers(T::list_helpers());
+  verify_available_helpers(Helpers<T>::list);
 }
 
 #define IGNORE_TEST(test_case_name, test_name)                 \
@@ -936,10 +1256,10 @@ void test_available_helpers() {
     expect_no_error();                 \
   } while (false)
 
-#define GET_EXPECT_ERROR(url, secret, error) \
-  if (tester.has_get()) {                    \
-    std::string s;                           \
-    MY_EXPECT_ERROR(get(url, &s), error);    \
+#define GET_EXPECT_ERROR(id, secret, error) \
+  if (tester.has_get()) {                   \
+    std::string s;                          \
+    MY_EXPECT_ERROR(get(id, &s), error);    \
   }
 
 #define INVALID_URL_ERROR "Invalid URL"
@@ -984,20 +1304,20 @@ void test_available_helpers() {
     op("http://user@host:3306/schema?tls-version=xyz", "password"); \
   } while (false)
 
-#define STORE_AND_CHECK(url, secret)                  \
-  do {                                                \
-    SCOPED_TRACE(url + std::string{" -> "} + secret); \
-    MY_EXPECT_NO_ERROR(store(url, secret));           \
-    expect_contains(url, secret);                     \
+#define STORE_AND_CHECK(id, secret)                  \
+  do {                                               \
+    SCOPED_TRACE(id + std::string{" -> "} + secret); \
+    MY_EXPECT_NO_ERROR(store(id, secret));           \
+    expect_contains(id, secret);                     \
   } while (false)
 
-#define STORE_AND_ERASE(url, secret)                  \
-  do {                                                \
-    SCOPED_TRACE(url + std::string{" -> "} + secret); \
-    MY_EXPECT_NO_ERROR(store(url, secret));           \
-    expect_contains(url, secret);                     \
-    MY_EXPECT_NO_ERROR(erase(url));                   \
-    expect_not_contain(url);                          \
+#define STORE_AND_ERASE(id, secret)                  \
+  do {                                               \
+    SCOPED_TRACE(id + std::string{" -> "} + secret); \
+    MY_EXPECT_NO_ERROR(store(id, secret));           \
+    expect_contains(id, secret);                     \
+    MY_EXPECT_NO_ERROR(erase(id));                   \
+    expect_not_contains(id);                         \
   } while (false)
 
 #ifdef _WIN32
@@ -1018,10 +1338,10 @@ void test_available_helpers() {
     op("user@host:33060", "six");                                           \
     op("ssh://user@host.com:22", "test");                                   \
     op("file:/user/host/com", "test");                                      \
-    if (GetParam() == "login-path") {                                       \
+    if (is_login_path()) {                                                  \
       op("user@host:55555",                                                 \
          "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"   \
-         "abcdefghijklmn");                                                 \
+         "abcdefghijklm");                                                  \
     } else {                                                                \
       op("user@host:55555",                                                 \
          "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"   \
@@ -1096,22 +1416,23 @@ void test_available_helpers() {
   }                                                                           \
                                                                               \
   TEST_P(test_case_name, list) {                                              \
-    std::set<std::string> urls{"user@host", "user@host:3306",                 \
-                               "user@host:33060", URL_WITH_SOCKET};           \
-    for (const auto &url : urls) {                                            \
-      STORE_AND_CHECK(url, url);                                              \
+    std::set<std::string> ids{"user@host", "user@host:3306",                  \
+                              "user@host:33060", URL_WITH_SOCKET};            \
+    for (const auto &id : ids) {                                              \
+      STORE_AND_CHECK(id, id);                                                \
     }                                                                         \
-    expect_list(urls);                                                        \
-    STORE_AND_CHECK(*urls.begin(), "new pass");                               \
-    expect_list(urls);                                                        \
+    expect_list(ids);                                                         \
+    /* overwrite a value */                                                   \
+    STORE_AND_CHECK(*ids.begin(), "new pass");                                \
+    expect_list(ids);                                                         \
   }                                                                           \
                                                                               \
-  TEST_P(test_case_name, get_nonexistent_url) {                               \
+  TEST_P(test_case_name, get_nonexistent_id) {                                \
     constexpr auto error = "Could not find the secret";                       \
     GET_EXPECT_ERROR("user@nonexistent.host", "secret", error);               \
   }                                                                           \
                                                                               \
-  TEST_P(test_case_name, erase_nonexistent_url) {                             \
+  TEST_P(test_case_name, erase_nonexistent_id) {                              \
     constexpr auto error = "Could not find the secret";                       \
     MY_EXPECT_ERROR(erase("user@nonexistent.host"), error);                   \
   }                                                                           \
@@ -1123,11 +1444,11 @@ void test_available_helpers() {
   }                                                                           \
                                                                               \
   TEST(Helpers, test_case_name##_list_helpers) {                              \
-    test_available_helpers<test_case_name>();                                 \
+    test_available_helpers<test_case_name::Helper_t>();                       \
   }                                                                           \
                                                                               \
-  TEST_P(test_case_name, login_path_external_entry) {                         \
-    if (GetParam() != "login-path") {                                         \
+  PASSWORD_TEST(test_case_name, login_path_external_entry) {                  \
+    if (!is_login_path()) {                                                   \
       return;                                                                 \
     }                                                                         \
     Config_editor_invoker invoker;                                            \
@@ -1142,8 +1463,8 @@ void test_available_helpers() {
     invoker.clear();                                                          \
   }                                                                           \
                                                                               \
-  TEST_P(test_case_name, login_path_external_and_helper_entries) {            \
-    if (GetParam() != "login-path") {                                         \
+  PASSWORD_TEST(test_case_name, login_path_external_and_helper_entries) {     \
+    if (!is_login_path()) {                                                   \
       return;                                                                 \
     }                                                                         \
     Config_editor_invoker invoker;                                            \
@@ -1160,8 +1481,9 @@ void test_available_helpers() {
     invoker.clear();                                                          \
   }                                                                           \
                                                                               \
-  TEST_P(test_case_name, login_path_external_entry_with_port_and_socket) {    \
-    if (GetParam() != "login-path") {                                         \
+  PASSWORD_TEST(test_case_name,                                               \
+                login_path_external_entry_with_port_and_socket) {             \
+    if (!is_login_path()) {                                                   \
       return;                                                                 \
     }                                                                         \
     Config_editor_invoker invoker;                                            \
@@ -1194,8 +1516,8 @@ void test_available_helpers() {
     invoker.clear();                                                          \
   }                                                                           \
                                                                               \
-  TEST_P(test_case_name, login_path_ipv6_stored_by_config_editor) {           \
-    if (GetParam() != "login-path") {                                         \
+  PASSWORD_TEST(test_case_name, login_path_ipv6_stored_by_config_editor) {    \
+    if (!is_login_path()) {                                                   \
       return;                                                                 \
     }                                                                         \
     Config_editor_invoker invoker;                                            \
@@ -1215,8 +1537,8 @@ void test_available_helpers() {
     invoker.clear();                                                          \
   }                                                                           \
                                                                               \
-  TEST_P(test_case_name, login_path_ipv6_stored_by_helper) {                  \
-    if (GetParam() != "login-path") {                                         \
+  PASSWORD_TEST(test_case_name, login_path_ipv6_stored_by_helper) {           \
+    if (!is_login_path()) {                                                   \
       return;                                                                 \
     }                                                                         \
     Config_editor_invoker invoker;                                            \
@@ -1243,40 +1565,66 @@ void test_available_helpers() {
     invoker.clear();                                                          \
   }
 
-#define REGISTER_TESTS(test_case_name)                                 \
-  namespace {                                                          \
-  const auto test_case_name##_list = test_case_name::list_helpers();   \
-  }                                                                    \
-  INSTANTIATE_TEST_SUITE_P(Helpers, test_case_name,                    \
-                           ::testing::ValuesIn(test_case_name##_list), \
-                           g_format_parameter)
+#define ADD_SEPARATION_TESTS(test_case_name)                               \
+  TEST_P(test_case_name, separation_store_and_check) {                     \
+    MY_EXPECT_NO_ERROR(store(Secret_type::PASSWORD, "user@host", "pass")); \
+    MY_EXPECT_NO_ERROR(store(Secret_type::GENERIC, "root@host", "PASS"));  \
+    expect_contains(Secret_type::PASSWORD, "user@host", "pass");           \
+    expect_contains(Secret_type::GENERIC, "root@host", "PASS");            \
+    expect_not_contains(Secret_type::GENERIC, "user@host");                \
+    expect_not_contains(Secret_type::PASSWORD, "root@host");               \
+  }                                                                        \
+                                                                           \
+  TEST_P(test_case_name, separation_store_and_erase) {                     \
+    MY_EXPECT_NO_ERROR(store(Secret_type::PASSWORD, "user@host", "pass")); \
+    MY_EXPECT_NO_ERROR(store(Secret_type::GENERIC, "root@host", "PASS"));  \
+    MY_EXPECT_NO_ERROR(erase(Secret_type::PASSWORD, "user@host"));         \
+    expect_not_contains(Secret_type::PASSWORD, "user@host");               \
+    expect_contains(Secret_type::GENERIC, "root@host", "PASS");            \
+    MY_EXPECT_NO_ERROR(store(Secret_type::PASSWORD, "user@host", "pass")); \
+    MY_EXPECT_NO_ERROR(erase(Secret_type::GENERIC, "root@host"));          \
+    expect_contains(Secret_type::PASSWORD, "user@host", "pass");           \
+    expect_not_contains(Secret_type::GENERIC, "root@host");                \
+  }                                                                        \
+                                                                           \
+  TEST_P(test_case_name, separation_store_and_get) {                       \
+    MY_EXPECT_NO_ERROR(store(Secret_type::PASSWORD, "user@host", "pass")); \
+    MY_EXPECT_NO_ERROR(store(Secret_type::GENERIC, "root@host", "PASS"));  \
+    std::string secret;                                                    \
+    EXPECT_FALSE(get(Secret_type::GENERIC, "user@host", &secret));         \
+    EXPECT_FALSE(get(Secret_type::PASSWORD, "root@host", &secret));        \
+  }                                                                        \
+                                                                           \
+  TEST_P(test_case_name, separation_store_and_get_same_id) {               \
+    MY_EXPECT_NO_ERROR(store(Secret_type::PASSWORD, "user@host", "pass")); \
+    MY_EXPECT_NO_ERROR(store(Secret_type::GENERIC, "user@host", "PASS"));  \
+    std::string secret;                                                    \
+    if (get(Secret_type::PASSWORD, "user@host", &secret)) {                \
+      EXPECT_EQ("pass", secret);                                           \
+    }                                                                      \
+    if (get(Secret_type::GENERIC, "user@host", &secret)) {                 \
+      EXPECT_EQ("PASS", secret);                                           \
+    }                                                                      \
+  }                                                                        \
+                                                                           \
+  TEST_P(test_case_name, separation_store_and_list) {                      \
+    MY_EXPECT_NO_ERROR(store(Secret_type::PASSWORD, "user@host", "pass")); \
+    MY_EXPECT_NO_ERROR(store(Secret_type::GENERIC, "root@host", "PASS"));  \
+    expect_list(Secret_type::PASSWORD, {"user@host"});                     \
+    expect_list(Secret_type::GENERIC, {"root@host"});                      \
+  }
+
+#define REGISTER_TESTS(test_case_name)                              \
+  INSTANTIATE_TEST_SUITE_P(                                         \
+      Helpers, test_case_name,                                      \
+      ::testing::ValuesIn(Helpers<test_case_name::Helper_t>::list), \
+      g_format_parameter)
 
 }  // namespace
 
-#define VALIDATION_TEST TEST_P
-#define NORMALIZATION_TEST TEST_P
-
-class Mysql_secret_store_api_test
-    : public Parametrized_helper_test<Mysql_secret_store_api_tester> {};
-
-ADD_TESTS(Mysql_secret_store_api_test)
-
-#undef VALIDATION_TEST
-#undef NORMALIZATION_TEST
-
-TEST_P(Mysql_secret_store_api_test, helper_name) {
-  EXPECT_EQ(GetParam(), tester.name());
-}
-
-TEST_P(Mysql_secret_store_api_test, get_nullptr) {
-  EXPECT_FALSE(get("url", nullptr));
-  expect_error("Invalid pointer");
-}
-
-TEST_P(Mysql_secret_store_api_test, list_nullptr) {
-  EXPECT_FALSE(tester.list(nullptr));
-  expect_error("Invalid pointer");
-}
+////////////////////////////////////////////////////////////////////////////////
+// API tests which do not depend on a helper's type
+////////////////////////////////////////////////////////////////////////////////
 
 TEST(Helpers, Mysql_secret_store_api_test_secret_spec_operators) {
   Secret_spec one{Secret_type::PASSWORD, "first URL"};
@@ -1285,7 +1633,7 @@ TEST(Helpers, Mysql_secret_store_api_test_secret_spec_operators) {
   EXPECT_FALSE(one == two);
   EXPECT_TRUE(one != two);
 
-  one.url = two.url;
+  one.id = two.id;
 
   EXPECT_TRUE(one == two);
   EXPECT_FALSE(one != two);
@@ -1308,105 +1656,228 @@ TEST(Helpers, Mysql_secret_store_api_test_get_available_helpers_invalid_dir) {
       Mysql_secret_store_api_tester::call_get_available_helpers(home)));
 }
 
+////////////////////////////////////////////////////////////////////////////////
+// API tests which do not depend on a secrets's type
+////////////////////////////////////////////////////////////////////////////////
+
+class Mysql_secret_store_api_test
+    : public Parametrized_helper_test<Mysql_secret_store_api_tester> {};
+
+TEST_P(Mysql_secret_store_api_test, helper_name) {
+  EXPECT_EQ(helper_name(), tester.name());
+}
+
+TEST_P(Mysql_secret_store_api_test, get_nullptr) {
+  EXPECT_FALSE(tester.get(Secret_type::PASSWORD, "url", nullptr));
+  expect_error("Invalid pointer");
+}
+
+TEST_P(Mysql_secret_store_api_test, list_nullptr) {
+  EXPECT_FALSE(tester.list(nullptr));
+  expect_error("Invalid pointer");
+}
+
+ADD_SEPARATION_TESTS(Mysql_secret_store_api_test);
+
 REGISTER_TESTS(Mysql_secret_store_api_test);
 
+////////////////////////////////////////////////////////////////////////////////
+// API tests with Secret_type::PASSWORD
+////////////////////////////////////////////////////////////////////////////////
+
+#define VALIDATION_TEST TEST_P
+#define NORMALIZATION_TEST TEST_P
+#define PASSWORD_TEST TEST_P
+
+class Mysql_secret_store_api_test_password
+    : public Parametrized_helper_test_with_type<Mysql_secret_store_api_tester,
+                                                Secret_type::PASSWORD> {};
+
+ADD_TESTS(Mysql_secret_store_api_test_password)
+
+#undef VALIDATION_TEST
+#undef NORMALIZATION_TEST
+#undef PASSWORD_TEST
+
+REGISTER_TESTS(Mysql_secret_store_api_test_password);
+
+////////////////////////////////////////////////////////////////////////////////
+// API tests with Secret_type::GENERIC
+////////////////////////////////////////////////////////////////////////////////
+
+#define VALIDATION_TEST IGNORE_TEST
+#define NORMALIZATION_TEST IGNORE_TEST
+#define PASSWORD_TEST IGNORE_TEST
+
+class Mysql_secret_store_api_test_generic
+    : public Parametrized_helper_test_with_type<Mysql_secret_store_api_tester,
+                                                Secret_type::GENERIC> {};
+
+ADD_TESTS(Mysql_secret_store_api_test_generic)
+
+#undef VALIDATION_TEST
+#undef NORMALIZATION_TEST
+#undef PASSWORD_TEST
+
+REGISTER_TESTS(Mysql_secret_store_api_test_generic);
+
+////////////////////////////////////////////////////////////////////////////////
+// Separation tests of the shell.*Credential() and shell.*Secret() functions
+////////////////////////////////////////////////////////////////////////////////
+
+class Shell_all_api_test
+    : public Parametrized_helper_test<Shell_all_api_tester> {};
+
+ADD_SEPARATION_TESTS(Shell_all_api_test);
+
+TEST_P(Shell_all_api_test, separation_delete_all_secrets) {
+  MY_EXPECT_NO_ERROR(store(Secret_type::PASSWORD, "user@host", "pass"));
+  MY_EXPECT_NO_ERROR(store(Secret_type::GENERIC, "root@host", "PASS"));
+
+  tester.tester(Secret_type::PASSWORD)->expect_delete_all_credentials();
+  expect_list(Secret_type::PASSWORD, {});
+  expect_list(Secret_type::GENERIC, {"root@host"});
+
+  MY_EXPECT_NO_ERROR(store(Secret_type::PASSWORD, "user@host", "pass"));
+
+  tester.tester(Secret_type::GENERIC)->expect_delete_all_credentials();
+  expect_list(Secret_type::PASSWORD, {"user@host"});
+  expect_list(Secret_type::GENERIC, {});
+}
+
+REGISTER_TESTS(Shell_all_api_test);
+
+////////////////////////////////////////////////////////////////////////////////
+// Tests of the shell.*Credential() functions
+////////////////////////////////////////////////////////////////////////////////
+
 #define VALIDATION_TEST TEST_P
 #define NORMALIZATION_TEST TEST_P
+#define PASSWORD_TEST TEST_P
 
-class Shell_api_test : public Parametrized_helper_test<Shell_api_tester> {};
+class Shell_credential_api_test
+    : public Parametrized_helper_test_with_type<Shell_credential_api_tester,
+                                                Secret_type::PASSWORD> {};
 
-ADD_TESTS(Shell_api_test)
+ADD_TESTS(Shell_credential_api_test)
 
 #undef VALIDATION_TEST
 #undef NORMALIZATION_TEST
+#undef PASSWORD_TEST
 
-TEST_P(Shell_api_test, delete_all_credentials) {
-  std::set<std::string> urls{"user@host", "user@host:3306", "user@host:33060",
-                             URL_WITH_SOCKET};
-  for (const auto &url : urls) {
-    STORE_AND_CHECK(url, url);
+TEST_P(Shell_credential_api_test, delete_all_credentials) {
+  std::set<std::string> ids{"user@host", "user@host:3306", "user@host:33060",
+                            URL_WITH_SOCKET};
+  for (const auto &id : ids) {
+    STORE_AND_CHECK(id, id);
   }
   tester.expect_delete_all_credentials();
   expect_list({});
 }
 
-REGISTER_TESTS(Shell_api_test);
+REGISTER_TESTS(Shell_credential_api_test);
+
+////////////////////////////////////////////////////////////////////////////////
+// Tests of the shell.*Secret() functions
+////////////////////////////////////////////////////////////////////////////////
 
 #define VALIDATION_TEST IGNORE_TEST
 #define NORMALIZATION_TEST IGNORE_TEST
+#define PASSWORD_TEST IGNORE_TEST
 
-class Helper_executable_test
-    : public Parametrized_helper_test<Helper_executable_tester> {};
+class Shell_secret_api_test
+    : public Parametrized_helper_test_with_type<Shell_secret_api_tester,
+                                                Secret_type::GENERIC> {};
 
-ADD_TESTS(Helper_executable_test)
+ADD_TESTS(Shell_secret_api_test)
 
 #undef VALIDATION_TEST
 #undef NORMALIZATION_TEST
+#undef PASSWORD_TEST
+
+TEST_P(Shell_secret_api_test, delete_all_secrets) {
+  std::set<std::string> ids{"user@host", "user@host:3306", "user@host:33060",
+                            URL_WITH_SOCKET};
+  for (const auto &id : ids) {
+    STORE_AND_CHECK(id, id);
+  }
+  tester.expect_delete_all_credentials();
+  expect_list({});
+}
+
+REGISTER_TESTS(Shell_secret_api_test);
+
+////////////////////////////////////////////////////////////////////////////////
+// Tests of helper executables which do not depend on secret's type
+////////////////////////////////////////////////////////////////////////////////
+
+class Helper_executable_test
+    : public Parametrized_helper_test<Helper_executable_tester> {};
 
 TEST_P(Helper_executable_test, uri_should_not_be_validated) {
   std::string output;
-  std::string spec = R"("ServerURL":"some+string","SecretType":"password")";
+  const auto secret_type = Secret_type::PASSWORD;
+  const std::string secret_id = "some+string";
   auto &invoker = tester.get_invoker();
 
-  EXPECT_TRUE(invoker.store("{" + spec + R"(,"Secret":"pass"})", &output));
+  EXPECT_TRUE(
+      invoker.store(tester.to_json(secret_type, secret_id, "pass"), &output));
   EXPECT_EQ("", output);
   output.clear();
 
-  EXPECT_TRUE(invoker.get("{" + spec + "}", &output));
+  EXPECT_TRUE(invoker.get(tester.to_json(secret_type, secret_id), &output));
   EXPECT_NE("", output);
   output.clear();
 
-  EXPECT_TRUE(invoker.erase("{" + spec + "}", &output));
+  EXPECT_TRUE(invoker.erase(tester.to_json(secret_type, secret_id), &output));
   EXPECT_EQ("", output);
   output.clear();
 }
 
 TEST_P(Helper_executable_test, secret_type_should_not_be_validated) {
-  if (GetParam() == "login-path") {
-    // login-path can only store passwords
-    return;
-  }
   std::string output;
-  std::string spec = R"("ServerURL":"user@host","SecretType":"some_type")";
+  const std::string secret_type = "some_type";
+  const std::string secret_id = "some+string";
   auto &invoker = tester.get_invoker();
 
-  EXPECT_TRUE(invoker.store("{" + spec + R"(,"Secret":"pass"})", &output));
+  EXPECT_TRUE(
+      invoker.store(tester.to_json(secret_type, secret_id, "pass"), &output));
   EXPECT_EQ("", output);
   output.clear();
 
-  EXPECT_TRUE(invoker.get("{" + spec + "}", &output));
+  EXPECT_TRUE(invoker.get(tester.to_json(secret_type, secret_id), &output));
   EXPECT_NE("", output);
   output.clear();
 
-  EXPECT_TRUE(invoker.erase("{" + spec + "}", &output));
+  EXPECT_TRUE(invoker.erase(tester.to_json(secret_type, secret_id), &output));
   EXPECT_EQ("", output);
   output.clear();
 }
 
 TEST_P(Helper_executable_test, invalid_command) {
   std::string output;
-  std::string spec = R"({"ServerURL":"user@host","SecretType":"password"})";
   auto &invoker = tester.get_invoker();
 
-  EXPECT_FALSE(invoker.invoke("unknown", spec, &output));
+  EXPECT_FALSE(invoker.invoke(
+      "unknown", tester.to_json(Secret_type::PASSWORD, "user@host"), &output));
   EXPECT_THAT(output, ::testing::HasSubstr("Unknown command"));
 }
 
 TEST_P(Helper_executable_test, missing_command) {
   std::string output;
-  std::string spec = R"({"ServerURL":"user@host","SecretType":"password"})";
   auto &invoker = tester.get_invoker();
 
-  EXPECT_FALSE(invoker.invoke(nullptr, spec, &output));
+  EXPECT_FALSE(invoker.invoke(
+      nullptr, tester.to_json(Secret_type::PASSWORD, "user@host"), &output));
   EXPECT_THAT(output, ::testing::HasSubstr("Missing command"));
 }
 
-TEST_P(Helper_executable_test, invalid_json_input_misspelled_server_url) {
-  const std::string error_message = R"("ServerURL" is missing)";
+TEST_P(Helper_executable_test, invalid_json_input_misspelled_secret_id) {
+  const std::string error_message = R"("SecretID" is missing)";
   auto &invoker = tester.get_invoker();
   std::string output;
 
-  for (const auto &s : {"ServerURl", "SErverURL", "ServerURLL", "SrverURL"}) {
+  for (const auto &s : {"SecretId", "SEcretID", "SecretIDD", "ScretID"}) {
     SCOPED_TRACE(s);
 
     std::string spec =
@@ -1436,7 +1907,7 @@ TEST_P(Helper_executable_test, invalid_json_input_misspelled_secret_type) {
     SCOPED_TRACE(s);
 
     std::string spec =
-        R"("ServerURL":"user@host",")" + std::string{s} + R"(":"password")";
+        R"("SecretID":"user@host",")" + std::string{s} + R"(":"password")";
 
     EXPECT_FALSE(invoker.store("{" + spec + R"(,"Secret":"pass"})", &output));
     EXPECT_THAT(output, ::testing::HasSubstr(error_message));
@@ -1460,7 +1931,7 @@ TEST_P(Helper_executable_test, invalid_json_input_misspelled_secret) {
   for (const auto &s : {"secret", "SEcret", "Secrett", "Scret"}) {
     SCOPED_TRACE(s);
 
-    std::string spec = R"({"ServerURL":"user@host","SecretType":"password",")" +
+    std::string spec = R"({"SecretID":"user@host","SecretType":"password",")" +
                        std::string{s} + R"(":"pass"})";
 
     EXPECT_FALSE(invoker.store(spec, &output));
@@ -1470,27 +1941,26 @@ TEST_P(Helper_executable_test, invalid_json_input_misspelled_secret) {
 }
 
 TEST_P(Helper_executable_test, invalid_json_input_unknown_member) {
-  const std::string error_message = "JSON object contains unknown member";
   auto &invoker = tester.get_invoker();
   std::string output;
   std::string spec =
-      R"("ServerURL":"user@host","SecretType":"password","InvalidMember":12345)";
+      R"("SecretID":"user@host","SecretType":"password","InvalidMember":12345)";
 
-  EXPECT_FALSE(invoker.store("{" + spec + R"(,"Secret":"pass"})", &output));
-  EXPECT_THAT(output, ::testing::HasSubstr(error_message));
+  EXPECT_TRUE(invoker.store("{" + spec + R"(,"Secret":"pass"})", &output));
+  EXPECT_EQ("", output);
   output.clear();
 
-  EXPECT_FALSE(invoker.get("{" + spec + "}", &output));
-  EXPECT_THAT(output, ::testing::HasSubstr(error_message));
+  EXPECT_TRUE(invoker.get("{" + spec + "}", &output));
+  EXPECT_THAT(output, ::testing::HasSubstr(R"("Secret":"pass")"));
   output.clear();
 
-  EXPECT_FALSE(invoker.erase("{" + spec + "}", &output));
-  EXPECT_THAT(output, ::testing::HasSubstr(error_message));
+  EXPECT_TRUE(invoker.erase("{" + spec + "}", &output));
+  EXPECT_EQ("", output);
   output.clear();
 }
 
-TEST_P(Helper_executable_test, invalid_json_input_missing_server_url) {
-  const std::string error_message = R"("ServerURL" is missing)";
+TEST_P(Helper_executable_test, invalid_json_input_missing_secret_id) {
+  const std::string error_message = R"("SecretID" is missing)";
   auto &invoker = tester.get_invoker();
   std::string output;
   std::string spec = R"("SecretType":"password")";
@@ -1512,7 +1982,7 @@ TEST_P(Helper_executable_test, invalid_json_input_missing_secret_type) {
   const std::string error_message = R"("SecretType" is missing)";
   auto &invoker = tester.get_invoker();
   std::string output;
-  std::string spec = R"("ServerURL":"user@host")";
+  std::string spec = R"("SecretID":"user@host")";
 
   EXPECT_FALSE(invoker.store("{" + spec + R"(,"Secret":"pass"})", &output));
   EXPECT_THAT(output, ::testing::HasSubstr(error_message));
@@ -1531,18 +2001,18 @@ TEST_P(Helper_executable_test, invalid_json_input_missing_secret) {
   const std::string error_message = R"("Secret" is missing)";
   auto &invoker = tester.get_invoker();
   std::string output;
-  std::string spec = R"({"ServerURL":"user@host","SecretType":"password"})";
+  std::string spec = R"({"SecretID":"user@host","SecretType":"password"})";
 
   EXPECT_FALSE(invoker.store(spec, &output));
   EXPECT_THAT(output, ::testing::HasSubstr(error_message));
   output.clear();
 }
 
-TEST_P(Helper_executable_test, invalid_json_input_wrong_type_server_url) {
-  const std::string error_message = R"("ServerURL" should be a string)";
+TEST_P(Helper_executable_test, invalid_json_input_wrong_type_secret_id) {
+  const std::string error_message = R"("SecretID" should be a string)";
   auto &invoker = tester.get_invoker();
   std::string output;
-  std::string spec = R"("ServerURL":false,"SecretType":"password")";
+  std::string spec = R"("SecretID":false,"SecretType":"password")";
 
   EXPECT_FALSE(invoker.store("{" + spec + R"(,"Secret":"pass"})", &output));
   EXPECT_THAT(output, ::testing::HasSubstr(error_message));
@@ -1561,7 +2031,7 @@ TEST_P(Helper_executable_test, invalid_json_input_wrong_type_secret_type) {
   const std::string error_message = R"("SecretType" should be a string)";
   auto &invoker = tester.get_invoker();
   std::string output;
-  std::string spec = R"("ServerURL":"user@host","SecretType":false)";
+  std::string spec = R"("SecretID":"user@host","SecretType":false)";
 
   EXPECT_FALSE(invoker.store("{" + spec + R"(,"Secret":"pass"})", &output));
   EXPECT_THAT(output, ::testing::HasSubstr(error_message));
@@ -1581,7 +2051,7 @@ TEST_P(Helper_executable_test, invalid_json_input_wrong_type_secret) {
   auto &invoker = tester.get_invoker();
   std::string output;
   std::string spec =
-      R"({"ServerURL":"user@host","SecretType":"password","Secret":false})";
+      R"({"SecretID":"user@host","SecretType":"password","Secret":false})";
 
   EXPECT_FALSE(invoker.store(spec, &output));
   EXPECT_THAT(output, ::testing::HasSubstr(error_message));
@@ -1596,6 +2066,263 @@ TEST_P(Helper_executable_test, version_command) {
   output.clear();
 }
 
+TEST_P(Helper_executable_test, deprecated_server_url) {
+  // ServerURL is deprecated, helpers still recognize it and reply with both
+  // ServerURL and SecretID
+  auto &invoker = tester.get_invoker();
+  std::string output;
+  std::string spec = R"("ServerURL":"user@host","SecretType":"password")";
+
+  EXPECT_TRUE(invoker.store("{" + spec + R"(,"Secret":"pass"})", &output));
+  EXPECT_EQ("", output);
+  output.clear();
+
+  EXPECT_TRUE(invoker.get("{" + spec + "}", &output));
+  EXPECT_EQ(
+      R"({"SecretType":"password","SecretID":"user@host","ServerURL":"user@host","Secret":"pass"})",
+      output);
+  output.clear();
+
+  EXPECT_TRUE(invoker.erase("{" + spec + "}", &output));
+  EXPECT_EQ("", output);
+  output.clear();
+}
+
+TEST_P(Helper_executable_test, list_with_secret_type) {
+  auto &invoker = tester.get_invoker();
+  std::string output;
+
+  EXPECT_TRUE(invoker.store(
+      tester.to_json(Secret_type::PASSWORD, "user@host", "pass"), &output));
+  EXPECT_EQ("", output);
+  output.clear();
+
+  EXPECT_TRUE(invoker.store(
+      tester.to_json(Secret_type::GENERIC, "some-id", "PASS"), &output));
+  EXPECT_EQ("", output);
+  output.clear();
+
+  EXPECT_TRUE(invoker.list(&output, R"({"SecretType":"password"})"));
+  EXPECT_EQ(
+      R"([{"SecretType":"password","SecretID":"user@host","ServerURL":"user@host"}])",
+      output);
+  output.clear();
+
+  EXPECT_TRUE(invoker.list(&output, R"({"SecretType":"generic"})"));
+  EXPECT_EQ(
+      R"([{"SecretType":"generic","SecretID":"some-id","ServerURL":"some-id"}])",
+      output);
+  output.clear();
+}
+
+TEST_P(Helper_executable_test, valid_id_characters) {
+  const std::unordered_set<char> unsupported_keychain = {
+      '\x00',
+      '\x0A',
+  };
+  const auto unsupported_secret_service = []() {
+    std::unordered_set<char> invalid;
+
+    // secret service only supports UTF-8 strings
+    for (int i = 128; i <= 255; ++i) {
+      invalid.emplace(static_cast<char>(i));
+    }
+
+    return invalid;
+  }();
+  std::unordered_set<char> unsupported;
+
+  if (is_keychain()) {
+    unsupported = unsupported_keychain;
+  } else if (is_secret_service()) {
+    unsupported = unsupported_secret_service;
+  }
+
+  const auto TEST_SECRET_ID = [&invoker = tester.get_invoker(),
+                               this](const std::string &secret_id) {
+    std::string output;
+    const std::string secret = "pass";
+    std::string actual_secret;
+
+    EXPECT_TRUE(invoker.store(
+        tester.to_json(Secret_type::GENERIC, secret_id, secret), &output));
+    EXPECT_EQ("", output);
+    output.clear();
+
+    EXPECT_TRUE(
+        invoker.get(tester.to_json(Secret_type::GENERIC, secret_id), &output));
+    EXPECT_TRUE(tester.to_secret(output, nullptr, &actual_secret));
+    EXPECT_EQ(secret, actual_secret);
+    output.clear();
+    actual_secret.clear();
+
+    EXPECT_TRUE(invoker.erase(tester.to_json(Secret_type::GENERIC, secret_id),
+                              &output));
+    EXPECT_EQ("", output);
+    output.clear();
+  };
+
+  const auto TEST_INVALID_SECRET_ID = [&invoker = tester.get_invoker(),
+                                       this](const std::string &secret_id) {
+    std::string output;
+    const std::string secret = "pass";
+
+    EXPECT_FALSE(invoker.store(
+        tester.to_json(Secret_type::GENERIC, secret_id, secret), &output));
+    EXPECT_EQ("Secret's ID contains a disallowed character", output);
+    output.clear();
+  };
+
+  for (int i = 0; i <= 255; ++i) {
+    const auto c = static_cast<char>(i);
+
+    if (unsupported.count(c)) {
+      continue;
+    }
+
+    SCOPED_TRACE("Character: " + std::to_string(c));
+    TEST_SECRET_ID(std::string(1, c));
+  }
+
+  for (const std::string utf8 : {"¢", "€", "𐍈"}) {
+    SCOPED_TRACE("String: " + utf8);
+    TEST_SECRET_ID(utf8);
+  }
+
+  for (const auto c : unsupported) {
+    SCOPED_TRACE("Character: " + std::to_string(c));
+    TEST_INVALID_SECRET_ID(std::string(1, c));
+  }
+}
+
+TEST_P(Helper_executable_test, valid_secret_characters) {
+  const std::unordered_set<char> unsupported_login_path = {
+      '\x00', '\x03', '\x04', '\x0A', '\x0D', '\x0F', '\x11', '\x12',
+      '\x13', '\x15', '\x16', '\x17', '\x19', '\x1A', '\x1C', '\x7F',
+  };
+  const std::unordered_set<char> unsupported_keychain = {
+      '\x00',
+      '\x0A',
+  };
+  const auto unsupported_secret_service = []() {
+    std::unordered_set<char> invalid;
+
+    // secret service only supports UTF-8 strings
+    for (int i = 128; i <= 255; ++i) {
+      invalid.emplace(static_cast<char>(i));
+    }
+
+    return invalid;
+  }();
+  std::unordered_set<char> unsupported;
+
+  if (is_login_path()) {
+    unsupported = unsupported_login_path;
+  } else if (is_keychain()) {
+    unsupported = unsupported_keychain;
+  } else if (is_secret_service()) {
+    unsupported = unsupported_secret_service;
+  }
+
+  const auto TEST_SECRET = [&invoker = tester.get_invoker(),
+                            this](const std::string &secret) {
+    std::string output;
+    const std::string secret_id = "some-id";
+    std::string actual_secret;
+
+    EXPECT_TRUE(invoker.store(
+        tester.to_json(Secret_type::GENERIC, secret_id, secret), &output));
+    EXPECT_EQ("", output);
+    output.clear();
+
+    EXPECT_TRUE(
+        invoker.get(tester.to_json(Secret_type::GENERIC, secret_id), &output));
+    EXPECT_TRUE(tester.to_secret(output, nullptr, &actual_secret));
+    EXPECT_EQ(secret, actual_secret);
+    output.clear();
+    actual_secret.clear();
+
+    EXPECT_TRUE(invoker.erase(tester.to_json(Secret_type::GENERIC, secret_id),
+                              &output));
+    EXPECT_EQ("", output);
+    output.clear();
+  };
+
+  const auto TEST_INVALID_SECRET = [&invoker = tester.get_invoker(),
+                                    this](const std::string &secret) {
+    std::string output;
+    const std::string secret_id = "some-id";
+
+    EXPECT_FALSE(invoker.store(
+        tester.to_json(Secret_type::GENERIC, secret_id, secret), &output));
+    EXPECT_EQ("Secret contains a disallowed character", output);
+    output.clear();
+  };
+
+  for (int i = 0; i <= 255; ++i) {
+    const auto c = static_cast<char>(i);
+
+    if (unsupported.count(c)) {
+      continue;
+    }
+
+    SCOPED_TRACE("Character: " + std::to_string(c));
+    TEST_SECRET(std::string(1, c));
+  }
+
+  for (const std::string utf8 : {"¢", "€", "𐍈"}) {
+    SCOPED_TRACE("String: " + utf8);
+    TEST_SECRET(utf8);
+  }
+
+  for (const auto c : unsupported) {
+    SCOPED_TRACE("Character: " + std::to_string(c));
+    TEST_INVALID_SECRET(std::string(1, c));
+  }
+}
+
+ADD_SEPARATION_TESTS(Helper_executable_test);
+
 REGISTER_TESTS(Helper_executable_test);
 
+////////////////////////////////////////////////////////////////////////////////
+// Tests of helper executables with Secret_type::PASSWORD
+////////////////////////////////////////////////////////////////////////////////
+
+#define VALIDATION_TEST IGNORE_TEST
+#define NORMALIZATION_TEST IGNORE_TEST
+#define PASSWORD_TEST TEST_P
+
+class Helper_executable_test_password
+    : public Parametrized_helper_test_with_type<Helper_executable_tester,
+                                                Secret_type::PASSWORD> {};
+
+ADD_TESTS(Helper_executable_test_password)
+
+#undef VALIDATION_TEST
+#undef NORMALIZATION_TEST
+#undef PASSWORD_TEST
+
+REGISTER_TESTS(Helper_executable_test_password);
+
+////////////////////////////////////////////////////////////////////////////////
+// Tests of helper executables with Secret_type::GENERIC
+////////////////////////////////////////////////////////////////////////////////
+
+#define VALIDATION_TEST IGNORE_TEST
+#define NORMALIZATION_TEST IGNORE_TEST
+#define PASSWORD_TEST IGNORE_TEST
+
+class Helper_executable_test_generic
+    : public Parametrized_helper_test_with_type<Helper_executable_tester,
+                                                Secret_type::GENERIC> {};
+
+ADD_TESTS(Helper_executable_test_generic)
+
+#undef VALIDATION_TEST
+#undef NORMALIZATION_TEST
+#undef PASSWORD_TEST
+
+REGISTER_TESTS(Helper_executable_test_generic);
+
 }  // namespace tests
diff --git a/unittest/mysqlshdk/libs/aws/aws_config_file_t.cc b/unittest/mysqlshdk/libs/aws/aws_config_file_t.cc
index 8808e4d534..77f431621f 100644
--- a/unittest/mysqlshdk/libs/aws/aws_config_file_t.cc
+++ b/unittest/mysqlshdk/libs/aws/aws_config_file_t.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -80,15 +80,78 @@ aws_secret_access_key=key1
 TEST_F(Aws_config_file_test, missing_file) {
   Aws_config_file config{"some-random-non-existing-file.txt"};
 
-  EXPECT_FALSE(config.load());
+  EXPECT_FALSE(config.load(false));
 }
 
-TEST_F(Aws_config_file_test, existing_file) {
+TEST_F(Aws_config_file_test, existing_config_file) {
   const std::string path = "zażółć";
   create_config(path);
   Aws_config_file config{path};
 
-  const auto profiles = config.load();
+  // require profile sections to be prefixed
+  const auto profiles = config.load(true);
+  ASSERT_TRUE(profiles);
+
+  {
+    const std::string profile_name = "default";
+    EXPECT_TRUE(profiles->exists(profile_name));
+
+    const auto profile = profiles->get(profile_name);
+    ASSERT_NE(nullptr, profile);
+
+    EXPECT_TRUE(profile->access_key_id());
+    EXPECT_EQ("id", *profile->access_key_id());
+    EXPECT_FALSE(profile->has("aws_access_key_id"));
+
+    EXPECT_TRUE(profile->secret_access_key());
+    EXPECT_EQ("key", *profile->secret_access_key());
+    EXPECT_FALSE(profile->has("aws_secret_access_key"));
+
+    EXPECT_FALSE(profile->session_token());
+    EXPECT_FALSE(profile->has("aws_session_token"));
+
+    EXPECT_FALSE(profile->has("region"));
+
+    ASSERT_TRUE(profile->has("max_attempts"));
+    EXPECT_EQ("7", *profile->get("max_attempts"));
+  }
+
+  // the 'oci' profile section is not prefixed, it's not in the profiles
+  EXPECT_FALSE(profiles->exists("oci"));
+  EXPECT_EQ(nullptr, profiles->get("oci"));
+
+  {
+    const std::string profile_name = "xyz";
+    EXPECT_TRUE(profiles->exists(profile_name));
+
+    const auto profile = profiles->get(profile_name);
+    ASSERT_NE(nullptr, profile);
+
+    EXPECT_FALSE(profile->access_key_id());
+    EXPECT_FALSE(profile->has("aws_access_key_id"));
+
+    EXPECT_FALSE(profile->secret_access_key());
+    EXPECT_FALSE(profile->has("aws_secret_access_key"));
+
+    EXPECT_TRUE(profile->session_token());
+    EXPECT_EQ("", *profile->session_token());
+    EXPECT_FALSE(profile->has("aws_session_token"));
+
+    ASSERT_TRUE(profile->has("region"));
+    EXPECT_EQ("eu-central-1", *profile->get("region"));
+  }
+
+  EXPECT_FALSE(profiles->exists("missing"));
+  EXPECT_EQ(nullptr, profiles->get("missing"));
+}
+
+TEST_F(Aws_config_file_test, existing_credentials_file) {
+  const std::string path = "zażółć";
+  create_config(path);
+  Aws_config_file config{path};
+
+  // load all sections
+  const auto profiles = config.load(false);
   ASSERT_TRUE(profiles);
 
   {
@@ -137,8 +200,13 @@ TEST_F(Aws_config_file_test, existing_file) {
     EXPECT_FALSE(profile->has("region"));
   }
 
+  // all profiles are loaded, and profile prefix is not removed, 'xyz' profile
+  // is not in the profiles
+  EXPECT_FALSE(profiles->exists("xyz"));
+  EXPECT_EQ(nullptr, profiles->get("xyz"));
+
   {
-    const std::string profile_name = "xyz";
+    const std::string profile_name = "profile xyz";
     EXPECT_TRUE(profiles->exists(profile_name));
 
     const auto profile = profiles->get(profile_name);
@@ -171,7 +239,7 @@ region=eu-central-1
 )");
     Aws_config_file config{path};
 
-    EXPECT_THROW_MSG(config.load(), std::runtime_error,
+    EXPECT_THROW_MSG(config.load(false), std::runtime_error,
                      "Failed to parse config file '" + path +
                          "': setting without an associated profile, in line 1: "
                          "line without profile");
@@ -185,7 +253,7 @@ region=eu-central-1
     Aws_config_file config{path};
 
     EXPECT_THROW_MSG(
-        config.load(), std::runtime_error,
+        config.load(false), std::runtime_error,
         "Failed to parse config file '" + path +
             "': missing closing square bracket, in line 1: [ profile xyz");
   }
@@ -199,7 +267,7 @@ not a setting
     Aws_config_file config{path};
 
     EXPECT_THROW_MSG(
-        config.load(), std::runtime_error,
+        config.load(false), std::runtime_error,
         "Failed to parse config file '" + path +
             "': expected setting-name=value, in line 3: not a setting");
   }
diff --git a/unittest/mysqlshdk/libs/aws/s3_bucket_config_t.cc b/unittest/mysqlshdk/libs/aws/s3_bucket_config_t.cc
index c5ab73fc7a..435129c3bf 100644
--- a/unittest/mysqlshdk/libs/aws/s3_bucket_config_t.cc
+++ b/unittest/mysqlshdk/libs/aws/s3_bucket_config_t.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -111,7 +111,8 @@ class Aws_s3_bucket_config_test : public testing::Test {
 
   [[nodiscard]] static Cleanup write_default_credentials(
       const std::string &contents) {
-    return write_file(default_credentials_file(), contents);
+    return write_file(default_credentials_file(),
+                      shcore::str_replace(contents, "[profile ", "["));
   }
 
   [[nodiscard]] static Cleanup write_file(const std::string &path,
diff --git a/unittest/mysqlshdk/libs/db/connection_options_t.cc b/unittest/mysqlshdk/libs/db/connection_options_t.cc
index 1f32c09f0a..58cb42a940 100644
--- a/unittest/mysqlshdk/libs/db/connection_options_t.cc
+++ b/unittest/mysqlshdk/libs/db/connection_options_t.cc
@@ -1,4 +1,4 @@
-/* Copyright (c) 2017, 2024, Oracle and/or its affiliates.
+/* Copyright (c) 2017, 2025, Oracle and/or its affiliates.
 
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License, version 2.0,
@@ -771,4 +771,43 @@ TEST(Connection_options, invalid_options_after_WL10912) {
   for (auto property : invalid_options) combine(property, callback);
 }
 
+TEST(Connection_options, local_infile) {
+  // Test rejection of invalid values
+  auto invalid_values = {"on", "off", "whatever", ""};
+
+  for (const auto &value : invalid_values) {
+    std::string uri("root@host?local-infile=");
+    uri.append(value);
+
+    std::string msg("Invalid value '");
+    msg.append(value);
+    msg.append("' for 'local-infile'. Allowed values: true, false, 1, 0.");
+
+    std::string uri_msg("Invalid URI: ");
+    uri_msg.append(msg);
+    MY_EXPECT_THROW(std::invalid_argument, uri_msg.c_str(),
+                    { mysqlshdk::db::Connection_options data(uri); });
+
+    mysqlshdk::db::Connection_options sample;
+    MY_EXPECT_THROW(std::invalid_argument, msg.c_str(),
+                    sample.set(mysqlshdk::db::kLocalInfile, value));
+  }
+
+  std::vector<std::string> valid_values = {"1", "true", "0", "false"};
+
+  for (const auto &value : valid_values) {
+    std::string uri("root@host?local-infile=");
+    uri.append(value);
+
+    EXPECT_NO_THROW({
+      mysqlshdk::db::Connection_options data(uri);
+      EXPECT_EQ(value, data.get(mysqlshdk::db::kLocalInfile));
+    });
+
+    mysqlshdk::db::Connection_options sample;
+    EXPECT_NO_THROW(sample.set(mysqlshdk::db::kLocalInfile, value));
+    EXPECT_EQ(value, sample.get(mysqlshdk::db::kLocalInfile));
+  }
+}
+
 }  // namespace testing
diff --git a/unittest/mysqlshdk/libs/mysql/instance_t.cc b/unittest/mysqlshdk/libs/mysql/instance_t.cc
index cb687292eb..dd3ab55dc0 100644
--- a/unittest/mysqlshdk/libs/mysql/instance_t.cc
+++ b/unittest/mysqlshdk/libs/mysql/instance_t.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2017, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -65,8 +65,6 @@ TEST_F(Instance_test, get_session) {
 TEST_F(Instance_test, get_sysvar_string_existing_variable) {
   EXPECT_CALL(session, do_connect(_connection_options));
   EXPECT_CALL(session, is_open()).WillOnce(Return(false));
-  const mysqlshdk::db::Connection_options opts;
-  EXPECT_CALL(session, get_connection_options()).WillOnce(ReturnRef(opts));
   _session->connect(_connection_options);
 
   mysqlshdk::mysql::Instance instance(_session);
@@ -109,8 +107,6 @@ TEST_F(Instance_test, get_sysvar_string_existing_variable) {
 TEST_F(Instance_test, get_sysvar_string_unexisting_variable) {
   EXPECT_CALL(session, do_connect(_connection_options));
   EXPECT_CALL(session, is_open()).WillOnce(Return(false));
-  const mysqlshdk::db::Connection_options opts;
-  EXPECT_CALL(session, get_connection_options()).WillOnce(ReturnRef(opts));
   _session->connect(_connection_options);
 
   session
@@ -140,8 +136,6 @@ TEST_F(Instance_test, get_sysvar_string_unexisting_variable) {
 TEST_F(Instance_test, get_sysvar_boolean_existing_variable) {
   EXPECT_CALL(session, do_connect(_connection_options));
   EXPECT_CALL(session, is_open()).WillOnce(Return(false));
-  const mysqlshdk::db::Connection_options opts;
-  EXPECT_CALL(session, get_connection_options()).WillOnce(ReturnRef(opts));
   _session->connect(_connection_options);
   mysqlshdk::mysql::Instance instance(_session);
 
@@ -195,8 +189,6 @@ TEST_F(Instance_test, get_sysvar_boolean_existing_variable) {
 TEST_F(Instance_test, get_sysvar_boolean_unexisting_variable) {
   EXPECT_CALL(session, do_connect(_connection_options));
   EXPECT_CALL(session, is_open()).WillOnce(Return(false));
-  const mysqlshdk::db::Connection_options opts;
-  EXPECT_CALL(session, get_connection_options()).WillOnce(ReturnRef(opts));
   _session->connect(_connection_options);
 
   session
@@ -226,8 +218,6 @@ TEST_F(Instance_test, get_sysvar_boolean_unexisting_variable) {
 TEST_F(Instance_test, get_sysvar_boolean_invalid_variable) {
   EXPECT_CALL(session, do_connect(_connection_options));
   EXPECT_CALL(session, is_open()).WillOnce(Return(false));
-  const mysqlshdk::db::Connection_options opts;
-  EXPECT_CALL(session, get_connection_options()).WillOnce(ReturnRef(opts));
   _session->connect(_connection_options);
 
   session
@@ -252,8 +242,6 @@ TEST_F(Instance_test, get_sysvar_boolean_invalid_variable) {
 TEST_F(Instance_test, get_sysvar_int_existing_variable) {
   EXPECT_CALL(session, do_connect(_connection_options));
   EXPECT_CALL(session, is_open()).WillOnce(Return(false));
-  const mysqlshdk::db::Connection_options opts;
-  EXPECT_CALL(session, get_connection_options()).WillOnce(ReturnRef(opts));
   _session->connect(_connection_options);
   mysqlshdk::mysql::Instance instance(_session);
 
@@ -309,8 +297,6 @@ TEST_F(Instance_test, get_sysvar_int_existing_variable) {
 TEST_F(Instance_test, get_sysvar_int_unexisting_variable) {
   EXPECT_CALL(session, do_connect(_connection_options));
   EXPECT_CALL(session, is_open()).WillOnce(Return(false));
-  const mysqlshdk::db::Connection_options opts;
-  EXPECT_CALL(session, get_connection_options()).WillOnce(ReturnRef(opts));
   _session->connect(_connection_options);
 
   session
@@ -340,8 +326,6 @@ TEST_F(Instance_test, get_sysvar_int_unexisting_variable) {
 TEST_F(Instance_test, get_sysvar_int_invalid_variable) {
   EXPECT_CALL(session, do_connect(_connection_options));
   EXPECT_CALL(session, is_open()).WillOnce(Return(false));
-  const mysqlshdk::db::Connection_options opts;
-  EXPECT_CALL(session, get_connection_options()).WillOnce(ReturnRef(opts));
   _session->connect(_connection_options);
 
   session
@@ -366,8 +350,6 @@ TEST_F(Instance_test, get_sysvar_int_invalid_variable) {
 TEST_F(Instance_test, get_sysvar_invalid_qualifier) {
   EXPECT_CALL(session, do_connect(_connection_options));
   EXPECT_CALL(session, is_open()).WillOnce(Return(false));
-  const mysqlshdk::db::Connection_options opts;
-  EXPECT_CALL(session, get_connection_options()).WillOnce(ReturnRef(opts));
   _session->connect(_connection_options);
 
   mysqlshdk::mysql::Instance instance(_session);
@@ -402,8 +384,6 @@ TEST_F(Instance_test, get_sysvar_invalid_qualifier) {
 TEST_F(Instance_test, set_sysvar) {
   EXPECT_CALL(session, do_connect(_connection_options));
   EXPECT_CALL(session, is_open()).WillOnce(Return(false));
-  const mysqlshdk::db::Connection_options opts;
-  EXPECT_CALL(session, get_connection_options()).WillOnce(ReturnRef(opts));
   _session->connect(_connection_options);
   mysqlshdk::mysql::Instance instance(_session);
 
@@ -715,8 +695,6 @@ TEST_F(Instance_test, set_sysvar) {
 TEST_F(Instance_test, set_sysvar_default) {
   EXPECT_CALL(session, do_connect(_connection_options));
   EXPECT_CALL(session, is_open()).WillOnce(Return(false));
-  const mysqlshdk::db::Connection_options opts;
-  EXPECT_CALL(session, get_connection_options()).WillOnce(ReturnRef(opts));
   _session->connect(_connection_options);
   mysqlshdk::mysql::Instance instance(_session);
   // Test set_sysvar_default with different scopes (GLOBAL and SESSION).
@@ -820,8 +798,6 @@ TEST_F(Instance_test, set_sysvar_default) {
 TEST_F(Instance_test, install_plugin_win) {
   EXPECT_CALL(session, do_connect(_connection_options));
   EXPECT_CALL(session, is_open()).WillOnce(Return(false));
-  const mysqlshdk::db::Connection_options opts;
-  EXPECT_CALL(session, get_connection_options()).WillOnce(ReturnRef(opts));
   _session->connect(_connection_options);
   mysqlshdk::mysql::Instance instance(_session);
 
@@ -847,8 +823,6 @@ TEST_F(Instance_test, install_plugin_win) {
 TEST_F(Instance_test, install_plugin_lin) {
   EXPECT_CALL(session, do_connect(_connection_options));
   EXPECT_CALL(session, is_open()).WillOnce(Return(false));
-  const mysqlshdk::db::Connection_options opts;
-  EXPECT_CALL(session, get_connection_options()).WillOnce(ReturnRef(opts));
   _session->connect(_connection_options);
   mysqlshdk::mysql::Instance instance(_session);
 
@@ -890,8 +864,6 @@ TEST_F(Instance_test, install_plugin_lin) {
 TEST_F(Instance_test, get_plugin_status) {
   EXPECT_CALL(session, do_connect(_connection_options));
   EXPECT_CALL(session, is_open()).WillOnce(Return(false));
-  const mysqlshdk::db::Connection_options opts;
-  EXPECT_CALL(session, get_connection_options()).WillOnce(ReturnRef(opts));
   _session->connect(_connection_options);
   mysqlshdk::mysql::Instance instance(_session);
 
@@ -933,8 +905,6 @@ TEST_F(Instance_test, get_plugin_status) {
 TEST_F(Instance_test, uninstall_plugin) {
   EXPECT_CALL(session, do_connect(_connection_options));
   EXPECT_CALL(session, is_open()).WillOnce(Return(false));
-  const mysqlshdk::db::Connection_options opts;
-  EXPECT_CALL(session, get_connection_options()).WillOnce(ReturnRef(opts));
   _session->connect(_connection_options);
 
   mysqlshdk::mysql::Instance instance(_session);
@@ -955,8 +925,6 @@ TEST_F(Instance_test, uninstall_plugin) {
 TEST_F(Instance_test, create_user) {
   EXPECT_CALL(session, do_connect(_connection_options));
   EXPECT_CALL(session, is_open()).WillOnce(Return(false));
-  const mysqlshdk::db::Connection_options opts;
-  EXPECT_CALL(session, get_connection_options()).WillOnce(ReturnRef(opts));
   _session->connect(_connection_options);
   mysqlshdk::mysql::Instance instance(_session);
 
@@ -1036,8 +1004,6 @@ TEST_F(Instance_test, create_user) {
 TEST_F(Instance_test, create_user_ssl_options) {
   EXPECT_CALL(session, do_connect(_connection_options));
   EXPECT_CALL(session, is_open()).WillOnce(Return(false));
-  const mysqlshdk::db::Connection_options opts;
-  EXPECT_CALL(session, get_connection_options()).WillOnce(ReturnRef(opts));
   _session->connect(_connection_options);
   mysqlshdk::mysql::Instance instance(_session);
 
@@ -1102,8 +1068,6 @@ TEST_F(Instance_test, create_user_ssl_options) {
 TEST_F(Instance_test, get_user_privileges_user_exists) {
   EXPECT_CALL(session, do_connect(_connection_options));
   EXPECT_CALL(session, is_open()).WillOnce(Return(false));
-  const mysqlshdk::db::Connection_options opts;
-  EXPECT_CALL(session, get_connection_options()).WillOnce(ReturnRef(opts));
   _session->connect(_connection_options);
 
   // user with some privileges
@@ -1140,8 +1104,6 @@ TEST_F(Instance_test, get_user_privileges_user_exists) {
 TEST_F(Instance_test, drop_user) {
   EXPECT_CALL(session, do_connect(_connection_options));
   EXPECT_CALL(session, is_open()).WillOnce(Return(false));
-  const mysqlshdk::db::Connection_options opts;
-  EXPECT_CALL(session, get_connection_options()).WillOnce(ReturnRef(opts));
   _session->connect(_connection_options);
   mysqlshdk::mysql::Instance instance(_session);
 
@@ -1180,8 +1142,6 @@ TEST_F(Instance_test, drop_user) {
 TEST_F(Instance_test, get_system_variables_like) {
   EXPECT_CALL(session, do_connect(_connection_options));
   EXPECT_CALL(session, is_open()).WillOnce(Return(false));
-  const mysqlshdk::db::Connection_options opts;
-  EXPECT_CALL(session, get_connection_options()).WillOnce(ReturnRef(opts));
   _session->connect(_connection_options);
   mysqlshdk::mysql::Instance instance(_session);
 
@@ -1267,8 +1227,6 @@ TEST_F(Instance_test, get_system_variables_like) {
 TEST_F(Instance_test, is_set_persist_supported) {
   EXPECT_CALL(session, do_connect(_connection_options));
   EXPECT_CALL(session, is_open()).WillOnce(Return(false));
-  const mysqlshdk::db::Connection_options opts;
-  EXPECT_CALL(session, get_connection_options()).WillOnce(ReturnRef(opts));
   _session->connect(_connection_options);
   mysqlshdk::mysql::Instance instance(_session);
 
@@ -1312,8 +1270,6 @@ TEST_F(Instance_test, is_set_persist_supported) {
 TEST_F(Instance_test, get_persisted_value) {
   EXPECT_CALL(session, do_connect(_connection_options));
   EXPECT_CALL(session, is_open()).WillOnce(Return(false));
-  const mysqlshdk::db::Connection_options opts;
-  EXPECT_CALL(session, get_connection_options()).WillOnce(ReturnRef(opts));
   _session->connect(_connection_options);
   mysqlshdk::mysql::Instance instance(_session);
 
diff --git a/unittest/mysqlshdk/libs/mysql/user_privileges_t.cc b/unittest/mysqlshdk/libs/mysql/user_privileges_t.cc
index 223bb3f542..8c16208db5 100644
--- a/unittest/mysqlshdk/libs/mysql/user_privileges_t.cc
+++ b/unittest/mysqlshdk/libs/mysql/user_privileges_t.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -290,9 +290,6 @@ class User_privileges_test : public tests::Shell_base_test {
 
   void TearDown() override {
     EXPECT_CALL(*m_session, do_close());
-    const mysqlshdk::db::Connection_options opts;
-    EXPECT_CALL(*m_session, get_connection_options())
-        .WillOnce(testing::ReturnRef(opts));
     EXPECT_CALL(*m_session, is_open()).WillOnce(Return(false));
     m_session->close();
   }
diff --git a/unittest/mysqlshdk/libs/ssh/ssh_connection_options_t.cc b/unittest/mysqlshdk/libs/ssh/ssh_connection_options_t.cc
index f1f92e13fc..40c8685e98 100644
--- a/unittest/mysqlshdk/libs/ssh/ssh_connection_options_t.cc
+++ b/unittest/mysqlshdk/libs/ssh/ssh_connection_options_t.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -283,6 +283,23 @@ TEST_F(Ssh_connection_options_t, remote_port_functions) {
   msg = "Internal option 'ssh-remote-port' has no value.";
   EXPECT_THROW_LIKE(ssh_config.get_remote_port(), std::runtime_error,
                     msg.c_str());
+
+  EXPECT_NO_THROW(ssh_config.set_fallback_remote_port(4406));
+  EXPECT_TRUE(ssh_config.has_remote_port());
+  EXPECT_EQ(4406, ssh_config.get_remote_port());
+
+  EXPECT_NO_THROW(ssh_config.set_remote_port(3306));
+  EXPECT_TRUE(ssh_config.has_remote_port());
+  EXPECT_EQ(3306, ssh_config.get_remote_port());
+
+  EXPECT_NO_THROW(ssh_config.clear_remote_port());
+  EXPECT_TRUE(ssh_config.has_remote_port());
+  EXPECT_EQ(4406, ssh_config.get_remote_port());
+
+  EXPECT_NO_THROW(ssh_config.clear_fallback_remote_port());
+  EXPECT_FALSE(ssh_config.has_remote_port());
+  EXPECT_THROW_LIKE(ssh_config.get_remote_port(), std::runtime_error,
+                    msg.c_str());
 }
 
 TEST_F(Ssh_connection_options_t, local_port_functions) {
diff --git a/unittest/mysqlshdk/libs/utils/string_list_matcher_t.cc b/unittest/mysqlshdk/libs/utils/string_list_matcher_t.cc
index 4e9246e4ee..9bcb7ecce4 100644
--- a/unittest/mysqlshdk/libs/utils/string_list_matcher_t.cc
+++ b/unittest/mysqlshdk/libs/utils/string_list_matcher_t.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2023, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -82,6 +82,8 @@ TEST(String_prefix_matcher, test_matching) {
                 "Sample   table"));
   EXPECT_FALSE(m.matches(
       "/* sample comment */ create /* unfinished comment Sample   table"));
+  EXPECT_TRUE(m.matches("# full line comment\n create Sample   table"));
+  EXPECT_TRUE(m.matches("-- full line comment\n create Sample   table"));
   EXPECT_TRUE(m.matches("Create\t\t\tSAMPLE\n\ntable"));
   EXPECT_TRUE(m.matches("   create   sample     table"));
   EXPECT_TRUE(m.matches("\n\tdelete   sample  view"));
diff --git a/unittest/scripts/auto/js_adminapi/scripts/_innodb_cluster_testing_api.js b/unittest/scripts/auto/js_adminapi/scripts/_innodb_cluster_testing_api.js
index 448d359679..3eb8060824 100644
--- a/unittest/scripts/auto/js_adminapi/scripts/_innodb_cluster_testing_api.js
+++ b/unittest/scripts/auto/js_adminapi/scripts/_innodb_cluster_testing_api.js
@@ -130,7 +130,7 @@ exports.test_cluster_rescan = function(primary_port, secondary_port, session) {
     if (shell.options.useWizards) {
         testutil.expectPrompt("Would you like to add it to the cluster metadata? [Y/n]: ", "y");
     } else {
-        options.addInstances="auto";
+        options.addUnmanaged=true;
     }
     cluster.rescan(options);
 
diff --git a/unittest/scripts/auto/js_adminapi/scripts/bug27089930_forcequorum_non_root_user.js b/unittest/scripts/auto/js_adminapi/scripts/bug27089930_forcequorum_non_root_user.js
deleted file mode 100644
index 4966c124db..0000000000
--- a/unittest/scripts/auto/js_adminapi/scripts/bug27089930_forcequorum_non_root_user.js
+++ /dev/null
@@ -1,67 +0,0 @@
-// Deploy instances.
-testutil.deploySandbox(__mysql_sandbox_port1, "root", {report_host: hostname});
-testutil.snapshotSandboxConf(__mysql_sandbox_port1);
-testutil.deploySandbox(__mysql_sandbox_port2, "root", {report_host: hostname});
-testutil.snapshotSandboxConf(__mysql_sandbox_port2);
-testutil.deploySandbox(__mysql_sandbox_port3, "root", {report_host: hostname});
-testutil.snapshotSandboxConf(__mysql_sandbox_port3);
-
-//@<> Configure instance 1 creating an Admin User.
-var cnfPath1 = testutil.getSandboxConfPath(__mysql_sandbox_port1);
-dba.configureInstance(__sandbox_uri1, {mycnfPath: cnfPath1, clusterAdmin: "gr_user", clusterAdminPassword: "gr_pass"});
-
-//@<> Configure instance 2 creating an Admin User.
-var cnfPath2 = testutil.getSandboxConfPath(__mysql_sandbox_port2);
-dba.configureInstance(__sandbox_uri2, {mycnfPath: cnfPath2, clusterAdmin: "gr_user", clusterAdminPassword: "gr_pass"});
-
-//@<> Configure instance 3 creating an Admin User.
-var cnfPath3 = testutil.getSandboxConfPath(__mysql_sandbox_port3);
-dba.configureInstance(__sandbox_uri3, {mycnfPath: cnfPath3, clusterAdmin: "gr_user", clusterAdminPassword: "gr_pass"});
-
-//@<> Create a cluster with 3 members.
-var admin_user_uri1 = "gr_user:gr_pass@localhost:"+__mysql_sandbox_port1;
-var admin_user_uri2 = "gr_user:gr_pass@localhost:"+__mysql_sandbox_port2;
-var admin_user_uri3 = "gr_user:gr_pass@localhost:"+__mysql_sandbox_port3;
-shell.connect(admin_user_uri1);
-var cluster = dba.createCluster("test_cluster", {gtidSetIsComplete: true});
-
-cluster.addInstance(admin_user_uri2);
-testutil.waitMemberState(__mysql_sandbox_port2, "ONLINE");
-
-cluster.addInstance(admin_user_uri3);
-testutil.waitMemberState(__mysql_sandbox_port3, "ONLINE");
-
-//@<> Show cluster status, all online.
-var status = cluster.status();
-EXPECT_EQ("ONLINE", status["defaultReplicaSet"]["topology"][`${hostname}:${__mysql_sandbox_port1}`]["status"])
-EXPECT_EQ("ONLINE", status["defaultReplicaSet"]["topology"][`${hostname}:${__mysql_sandbox_port2}`]["status"])
-EXPECT_EQ("ONLINE", status["defaultReplicaSet"]["topology"][`${hostname}:${__mysql_sandbox_port3}`]["status"])
-
-//@<> Kill instance 2
-testutil.killSandbox(__mysql_sandbox_port2);
-// Since the cluster has quorum, the instance will be kicked off the
-// Cluster going OFFLINE->UNREACHABLE->(MISSING)
-testutil.waitMemberState(__mysql_sandbox_port2, "(MISSING)");
-
-//@<> Kill instance 3
-testutil.killSandbox(__mysql_sandbox_port3);
-// Waiting for the third added instance to become unreachable
-// Will remain unreachable since there's no quorum to kick it off
-testutil.waitMemberState(__mysql_sandbox_port3, "UNREACHABLE");
-
-//@<> Cluster.forceQuorumUsingPartitionOf success
-cluster.forceQuorumUsingPartitionOf(admin_user_uri1);
-
-//@<> Show cluster status.
-var status = cluster.status();
-EXPECT_EQ("OK_NO_TOLERANCE_PARTIAL", status["defaultReplicaSet"]["status"])
-EXPECT_EQ("ONLINE", status["defaultReplicaSet"]["topology"][`${hostname}:${__mysql_sandbox_port1}`]["status"])
-EXPECT_EQ("(MISSING)", status["defaultReplicaSet"]["topology"][`${hostname}:${__mysql_sandbox_port2}`]["status"])
-EXPECT_EQ("(MISSING)", status["defaultReplicaSet"]["topology"][`${hostname}:${__mysql_sandbox_port3}`]["status"])
-
-// Clean-up deployed instances.
-session.close();
-cluster.disconnect();
-testutil.destroySandbox(__mysql_sandbox_port1);
-testutil.destroySandbox(__mysql_sandbox_port2, true);
-testutil.destroySandbox(__mysql_sandbox_port3, true);
diff --git a/unittest/scripts/auto/js_adminapi/scripts/bug27572618_user_missing_privileges.js b/unittest/scripts/auto/js_adminapi/scripts/bug27572618_user_missing_privileges.js
deleted file mode 100644
index c3298a2465..0000000000
--- a/unittest/scripts/auto/js_adminapi/scripts/bug27572618_user_missing_privileges.js
+++ /dev/null
@@ -1,29 +0,0 @@
-// Deploy Instances
-testutil.deploySandbox(__mysql_sandbox_port1, "root", {report_host: hostname});
-testutil.snapshotSandboxConf(__mysql_sandbox_port1);
-testutil.deploySandbox(__mysql_sandbox_port2, "root", {report_host: hostname});
-testutil.snapshotSandboxConf(__mysql_sandbox_port2);
-
-//@<> Configure instance 1 creating an Admin User.
-var cnfPath1 = testutil.getSandboxConfPath(__mysql_sandbox_port1);
-dba.configureInstance(__sandbox_uri1, {mycnfPath: cnfPath1, clusterAdmin: "gr_user", clusterAdminPassword: "gr_pass"});
-
-//@<> Configure instance 2 creating an Admin User.
-var cnfPath2 = testutil.getSandboxConfPath(__mysql_sandbox_port2);
-dba.configureInstance(__sandbox_uri2, {mycnfPath: cnfPath2, clusterAdmin: "gr_user", clusterAdminPassword: "gr_pass"});
-
-//@<> Create cluster.
-shell.connect("gr_user:gr_pass@localhost:"+__mysql_sandbox_port1);
-var cluster = dba.createCluster("test_cluster", {gtidSetIsComplete: true});
-
-cluster.addInstance("gr_user:gr_pass@localhost:"+__mysql_sandbox_port2);
-testutil.waitMemberState(__mysql_sandbox_port2, "ONLINE");
-
-//@<> Remove one instance using Admin user (cannot fail with lack of privileges).
-cluster.removeInstance("gr_user:gr_pass@localhost:"+__mysql_sandbox_port2);
-
-// Clean-up deployed instances.
-session.close();
-cluster.disconnect();
-testutil.destroySandbox(__mysql_sandbox_port1);
-testutil.destroySandbox(__mysql_sandbox_port2);
diff --git a/unittest/scripts/auto/js_adminapi/scripts/cluster_addinstance.js b/unittest/scripts/auto/js_adminapi/scripts/cluster_addinstance.js
index e113305900..457905147c 100644
--- a/unittest/scripts/auto/js_adminapi/scripts/cluster_addinstance.js
+++ b/unittest/scripts/auto/js_adminapi/scripts/cluster_addinstance.js
@@ -67,7 +67,7 @@ session2.runSql("SET GLOBAL offline_mode=1");
 session2.close();
 
 //@<> BUG#27084767: Create a cluster in single-primary mode
-shell.options["dba.logSql"] = 1;
+shell.options["logSql"] = "on";
 WIPE_SHELL_LOG();
 
 var c = dba.createCluster('test', {gtidSetIsComplete: true});
@@ -82,7 +82,7 @@ EXPECT_EQ(1, get_sysvar(session, "auto_increment_increment"));
 EXPECT_EQ(2, get_sysvar(session, "auto_increment_offset"));
 
 //@<> BUG#27084767: Add instance to cluster in single-primary mode
-shell.options["dba.logSql"] = 1;
+shell.options["logSql"] = "on";
 WIPE_SHELL_LOG();
 
 EXPECT_NO_THROWS(function(){ c.addInstance(__sandbox_uri2); });
diff --git a/unittest/scripts/auto/js_adminapi/scripts/cluster_addinstance_bugfixes.js b/unittest/scripts/auto/js_adminapi/scripts/cluster_addinstance_bugfixes.js
index 404809e4e3..96704f4189 100644
--- a/unittest/scripts/auto/js_adminapi/scripts/cluster_addinstance_bugfixes.js
+++ b/unittest/scripts/auto/js_adminapi/scripts/cluster_addinstance_bugfixes.js
@@ -75,7 +75,7 @@ testutil.startSandbox(__mysql_sandbox_port2);
 testutil.waitMemberState(__mysql_sandbox_port2, "ONLINE");
 
 //@<> Check if var name is correct  {VER(>=8.0.26)}
-shell.options["dba.logSql"] = 2;
+shell.options["logSql"] = "unfiltered";
 
 shell.connect(__sandbox_uri1);
 
diff --git a/unittest/scripts/auto/js_adminapi/scripts/cluster_rescan.js b/unittest/scripts/auto/js_adminapi/scripts/cluster_rescan.js
index cad55a9f21..082e819399 100644
--- a/unittest/scripts/auto/js_adminapi/scripts/cluster_rescan.js
+++ b/unittest/scripts/auto/js_adminapi/scripts/cluster_rescan.js
@@ -222,169 +222,17 @@ cluster.rescan();
 var final_atts = session.runSql(`SELECT attributes->'$.server_id' AS server_id FROM mysql_innodb_cluster_metadata.instances WHERE address = '${hostname}:${__mysql_sandbox_port3}'`).fetchOneObject();
 EXPECT_EQ(initial_atts.server_id, final_atts.server_id);
 
-//@<> WL10644 - TSF2_6: empty addInstances throw ArgumentError.
-EXPECT_THROWS(function() {
-    cluster.rescan({addInstances: []});
-}, `The list for 'addInstances' option cannot be empty.`);
-EXPECT_OUTPUT_CONTAINS("The 'addInstances' and 'removeInstances' options are deprecated. Please use 'addUnmanaged' and/or 'removeObsolete' instead.");
-
-//@<> WL10644 - TSF2_8: invalid addInstances list throw ArgumentError.
-EXPECT_THROWS(function() {
-    cluster.rescan({addInstances: [,]});
-}, `Invalid value 'undefined' for 'addInstances' option: Invalid connection options, expected either a URI or a Connection Options Dictionary`);
-EXPECT_THROWS(function() {
-    cluster.rescan({addInstances: ["localhost"]});
-}, `Invalid value 'localhost' for 'addInstances' option: port is missing.`);
-EXPECT_THROWS(function() {
-    cluster.rescan({addInstances: ["localhost:3300", ":3301"]});
-}, `Invalid value ':3301' for 'addInstances' option: host cannot be empty.`);
-EXPECT_THROWS(function() {
-    cluster.rescan({addInstances: ["localhost:3300", "@", "myhost:3301"]});
-}, `Invalid value '@' for 'addInstances' option: Invalid URI: Missing user information`);
-EXPECT_THROWS(function() {
-    cluster.rescan({addInstances: [{}]});
-}, `Invalid value '{}' for 'addInstances' option: Invalid connection options, no options provided.`);
-EXPECT_THROWS(function() {
-    cluster.rescan({addInstances: [{host: "myhost"}]});
-}, `Invalid value '{"host": "myhost"}' for 'addInstances' option: port is missing.`);
-EXPECT_THROWS(function() {
-    cluster.rescan({addInstances: [{host: "myhost", port:3300}, {host: ""}, {host: "localhost", port:3301}]});
-}, `Invalid value '{"host": ""}' for 'addInstances' option: Host value cannot be an empty string.`);
-
-//@<> WL10644: Duplicated values for addInstances.
-EXPECT_THROWS(function() {
-    cluster.rescan({addInstances: ["localhost:3301", "localhost:3300", "localhost:3301"]});
-}, `Duplicated value found for instance 'localhost:3301' in 'addInstances' option.`);
-EXPECT_THROWS(function() {
-    cluster.rescan({addInstances: [{host: "localhost", port: "3301", user: "root"}, {host: "localhost", port: "3300"}, {host: "localhost", port: "3301"}]});
-}, `Duplicated value found for instance 'localhost:3301' in 'addInstances' option.`);
-EXPECT_THROWS(function() {
-    cluster.rescan({addInstances: ["localhost:3301", "localhost:3300", {host: "localhost", port: "3301"}]});
-}, `Duplicated value found for instance 'localhost:3301' in 'addInstances' option.`);
-
-//@<> WL10644 - TSF2_9: invalid value with addInstances throw ArgumentError.
-EXPECT_THROWS(function() {
-    cluster.rescan({addInstances: "invalid"});
-}, `Option 'addInstances' only accepts 'auto' as a valid string value, otherwise a list of instances is expected.`);
-
-//@<> WL10644 - TSF2_7: "auto" is case insensitive, no error.
-EXPECT_NO_THROWS(function(){ cluster.rescan({addInstances: "AuTo"}); });
-
-//@<> WL10644: Invalid type used for addInstances.
-EXPECT_THROWS(function() {
-    cluster.rescan({addInstances: {}});
-}, `The 'addInstances' option must be a string or a list of strings.`);
-EXPECT_THROWS(function() {
-    cluster.rescan({addInstances: true});
-}, `The 'addInstances' option must be a string or a list of strings.`);
-EXPECT_THROWS(function() {
-    cluster.rescan({addInstances: 123});
-}, `The 'addInstances' option must be a string or a list of strings.`);
-
-//@<> WL10644 - TSF2_10: not active member in addInstances throw RuntimeError.
-cluster.rescan({addInstances: ["localhost:1111"]});
-
-//@<> WL10644 - TSF2_11: warning for already members in addInstances.
-var member_address = hostname + ":" + __mysql_sandbox_port1;
-cluster.rescan({addInstances: [member_address]});
-
-//@<> WL10644 - TSF3_6: empty removeInstances throw ArgumentError.
-EXPECT_THROWS(function() {
-    cluster.rescan({removeInstances: []});
-}, `The list for 'removeInstances' option cannot be empty.`);
-EXPECT_OUTPUT_CONTAINS("The 'addInstances' and 'removeInstances' options are deprecated. Please use 'addUnmanaged' and/or 'removeObsolete' instead.");
-
-//@<> WL10644 - TSF3_8: invalid removeInstances list throw ArgumentError.
-EXPECT_THROWS(function() {
-    cluster.rescan({removeInstances: [,]});
-}, `Invalid value 'undefined' for 'removeInstances' option: Invalid connection options, expected either a URI or a Connection Options Dictionary`);
-EXPECT_THROWS(function() {
-    cluster.rescan({removeInstances: ["localhost"]});
-}, `Invalid value 'localhost' for 'removeInstances' option: port is missing.`);
-EXPECT_THROWS(function() {
-    cluster.rescan({removeInstances: ["localhost:3300", ":3301"]});
-}, `Invalid value ':3301' for 'removeInstances' option: host cannot be empty.`);
-EXPECT_THROWS(function() {
-    cluster.rescan({removeInstances: ["localhost:3300", "@", "myhost:3301"]});
-}, `Invalid value '@' for 'removeInstances' option: Invalid URI: Missing user information`);
-EXPECT_THROWS(function() {
-    cluster.rescan({removeInstances: [{}]});
-}, `Invalid value '{}' for 'removeInstances' option: Invalid connection options, no options provided.`);
-EXPECT_THROWS(function() {
-    cluster.rescan({removeInstances: [{host: "myhost"}]});
-}, `Invalid value '{"host": "myhost"}' for 'removeInstances' option: port is missing.`);
-EXPECT_THROWS(function() {
-    cluster.rescan({removeInstances: [{host: "myhost", port:3300}, {port:3301}]});
-}, `Invalid value '{"port": 3301}' for 'removeInstances' option: The connection option 'host' has no value.`);
-EXPECT_THROWS(function() {
-    cluster.rescan({removeInstances: [{host: "myhost", port:3300}, {user: "root"}, {host: "localhost", port:3301}]});
-}, `Invalid value '{"user": "root"}' for 'removeInstances' option: The connection option 'host' has no value.`);
-EXPECT_THROWS(function() {
-    cluster.rescan({removeInstances: [{host: "myhost", port:3300}, {host: ""}, {host: "localhost", port:3301}]});
-}, `Invalid value '{"host": ""}' for 'removeInstances' option: Host value cannot be an empty string.`);
-
-//@<> WL10644: Duplicated values for removeInstances.
-EXPECT_THROWS(function() {
-    cluster.rescan({removeInstances: ["localhost:3301", "localhost:3300", "localhost:3301"]});
-}, `Duplicated value found for instance 'localhost:3301' in 'removeInstances' option.`);
-EXPECT_THROWS(function() {
-    cluster.rescan({removeInstances: [{host: "localhost", port: "3301", user: "root"}, {host: "localhost", port: "3300"}, {host: "localhost", port: "3301"}]});
-}, `Duplicated value found for instance 'localhost:3301' in 'removeInstances' option.`);
-EXPECT_THROWS(function() {
-    cluster.rescan({removeInstances: ["localhost:3301", "localhost:3300", {host: "localhost", port: "3301"}]});
-}, `Duplicated value found for instance 'localhost:3301' in 'removeInstances' option.`);
-
-//@<> WL10644 - TSF3_9: invalid value with removeInstances throw ArgumentError.
-EXPECT_THROWS(function() {
-    cluster.rescan({removeInstances: "invalid"});
-}, `Option 'removeInstances' only accepts 'auto' as a valid string value, otherwise a list of instances is expected.`);
-
-//@<> WL10644 - TSF3_7: "auto" is case insensitive, no error.
-EXPECT_NO_THROWS(function(){ cluster.rescan({removeInstances: "aUtO"}); });
-
-//@<> WL10644: Invalid type used for removeInstances.
-EXPECT_THROWS(function() {
-    cluster.rescan({removeInstances: {}});
-}, `The 'removeInstances' option must be a string or a list of strings.`);
-EXPECT_THROWS(function() {
-    cluster.rescan({removeInstances: true});
-}, `The 'removeInstances' option must be a string or a list of strings.`);
-EXPECT_THROWS(function() {
-    cluster.rescan({removeInstances: 123});
-}, `The 'removeInstances' option must be a string or a list of strings.`);
-
-//@<> Check bool type for addUnmanaged and removeObsolete
-EXPECT_THROWS(function() {
-    cluster.rescan({addUnmanaged: ""});
-}, `Option 'addUnmanaged' Bool expected, but value is String`);
-EXPECT_THROWS(function() {
-    cluster.rescan({removeObsolete: ""});
-}, `Option 'removeObsolete' Bool expected, but value is String`);
-
-//@<> Can't mix both new and deprecated options
-EXPECT_THROWS(function() {
-    cluster.rescan({addInstances: "auto", addUnmanaged: true});
-}, `Options 'addUnmanaged' and 'removeObsolete' are mutually exclusive with deprecated options 'addInstances' and 'removeInstances'. Mixing either one from both groups isn't allowed.`);
-EXPECT_THROWS(function() {
-    cluster.rescan({removeObsolete: true, addInstances: "auto"});
-}, `Options 'addUnmanaged' and 'removeObsolete' are mutually exclusive with deprecated options 'addInstances' and 'removeInstances'. Mixing either one from both groups isn't allowed.`);
-EXPECT_THROWS(function() {
-    cluster.rescan({removeInstances: "auto", addUnmanaged: true});
-}, `Options 'addUnmanaged' and 'removeObsolete' are mutually exclusive with deprecated options 'addInstances' and 'removeInstances'. Mixing either one from both groups isn't allowed.`);
-EXPECT_THROWS(function() {
-    cluster.rescan({removeObsolete: true, removeInstances: "auto"});
-}, `Options 'addUnmanaged' and 'removeObsolete' are mutually exclusive with deprecated options 'addInstances' and 'removeInstances'. Mixing either one from both groups isn't allowed.`);
-
-//@<> WL10644 - TSF3_10: active member in removeInstances throw RuntimeError.
-cluster.rescan({removeInstances: [member_address]});
-
-//@<> WL10644 - TSF3_11: warning for not members in removeInstances.
-cluster.rescan({removeInstances: ["localhost:1111"]});
-
-//@ WL10644: Duplicated values between addInstances and removeInstances.
-cluster.rescan({addInstances: ["localhost:3300", "localhost:3302", "localhost:3301"], removeInstances: ["localhost:3301", "localhost:3300"]});
-cluster.rescan({addInstances: [{host: "localhost", port: "3301", user: "root"}], removeInstances: [{host: "localhost", port: "3300"}, {host: "localhost", port: "3301"}]});
-cluster.rescan({addInstances: ["localhost:3301", "localhost:3300"], removeInstances: [{host: "localhost", port: "3301"}]});
+//@<> invalid addUnmanaged and removeObsolete value type throws TypeError
+EXPECT_THROWS_TYPE(function() {
+    cluster.rescan({addUnmanaged: "yes"})
+}, "Argument #1: Option 'addUnmanaged' Bool expected, but value is String", "TypeError");
+
+EXPECT_THROWS_TYPE(function() {
+    cluster.rescan({removeObsolete: "yes"})
+}, "Argument #1: Option 'removeObsolete' Bool expected, but value is String", "TypeError");
+
+//@<> ensure removeObsolete also accounts for missing server id.
+EXPECT_NO_THROWS(function(){ cluster.rescan({removeObsolete: true}); });
 
 //@<> Remove instance on port 2 and 3 from MD but keep it in the group.
 session.runSql("DELETE FROM mysql_innodb_cluster_metadata.instances WHERE instance_name=?", [hostname+":"+__mysql_sandbox_port2]);
@@ -393,10 +241,8 @@ validate_status(cluster.status(), [[__mysql_sandbox_port1, "RECOVERY_UNUSED"],
                                    [__mysql_sandbox_port2, "UNMANAGED"],
                                    [__mysql_sandbox_port3, "UNMANAGED"]]);
 
-//@<> WL10644 - TSF2_1: Rescan with addInstances:[complete_valid_list].
-var member_address2 = hostname + ":" + __mysql_sandbox_port2;
-var member_address3 = hostname + ":" + __mysql_sandbox_port3;
-cluster.rescan({addInstances: [member_address2, member_address3]});
+//@<> addUnmanaged must ensure all metadata is updated
+cluster.rescan({addUnmanaged: true});
 
 //@<> WL10644 - TSF2_1: Validate that the instances were added.
 validate_status(cluster.status(), [[__mysql_sandbox_port1, "OK"],
@@ -428,26 +274,10 @@ validate_status(cluster.status(), [[__mysql_sandbox_port1, "OK"],
                                    [__mysql_sandbox_port2, "OK"],
                                    [__mysql_sandbox_port3, "OK"]]);
 
-//@<> WL10644 - TSF2_2: Remove instances on port 2 and 3 from MD again.
-session.runSql("DELETE FROM mysql_innodb_cluster_metadata.instances WHERE instance_name=?", [member_address2]);
-session.runSql("DELETE FROM mysql_innodb_cluster_metadata.instances WHERE instance_name=?", [member_address3]);
-
-validate_status(cluster.status(), [[__mysql_sandbox_port1, "RECOVERY_UNUSED"],
-                                   [__mysql_sandbox_port2, "UNMANAGED"],
-                                   [__mysql_sandbox_port3, "UNMANAGED"]]);
-
-//@<> WL10644 - TSF2_2: Rescan with addInstances:[incomplete_valid_list] and interactive:true.
-shell.options.useWizards=1;
-testutil.expectPrompt("Would you like to add it to the cluster metadata? [Y/n]:", "y");
-cluster.rescan({addInstances: [member_address2]});
-shell.options.useWizards=0;
-
-//@<> WL10644 - TSF2_2: Validate that the instances were added.
-validate_status(cluster.status(), [[__mysql_sandbox_port1, "OK"],
-                                   [__mysql_sandbox_port2, "OK"],
-                                   [__mysql_sandbox_port3, "OK"]]);
+//@<> Make sure that "addUnmanaged: true" works.
+var member_address2 = hostname + ":" + __mysql_sandbox_port2;
+var member_address3 = hostname + ":" + __mysql_sandbox_port3;
 
-//@<> WL10644 - TSF2_3: Remove instances on port 2 and 3 from MD again.
 session.runSql("DELETE FROM mysql_innodb_cluster_metadata.instances WHERE instance_name=?", [member_address2]);
 session.runSql("DELETE FROM mysql_innodb_cluster_metadata.instances WHERE instance_name=?", [member_address3]);
 
@@ -455,39 +285,7 @@ validate_status(cluster.status(), [[__mysql_sandbox_port1, "RECOVERY_UNUSED"],
                                    [__mysql_sandbox_port2, "UNMANAGED"],
                                    [__mysql_sandbox_port3, "UNMANAGED"]]);
 
-//@<> WL10644 - TSF2_3: Rescan with addInstances:[incomplete_valid_list] and interactive:false.
-cluster.rescan({addInstances: [member_address2]});
-
-//@<> WL10644 - TSF2_3: Validate that the instances were added.
-validate_status(cluster.status(), [[__mysql_sandbox_port1, "OK"],
-                                   [__mysql_sandbox_port2, "OK"],
-                                   [__mysql_sandbox_port3, "UNMANAGED"]]);
-
-//@<> WL10644 - TSF2_4: Remove instances on port 2 from MD.
-session.runSql("DELETE FROM mysql_innodb_cluster_metadata.instances WHERE instance_name=?", [member_address2]);
-validate_status(cluster.status(), [[__mysql_sandbox_port1, "RECOVERY_UNUSED_SINGLE"],
-                                   [__mysql_sandbox_port2, "UNMANAGED"],
-                                   [__mysql_sandbox_port3, "UNMANAGED"]]);
-
-//@<> WL10644 - TSF2_4: Rescan with addInstances:"auto" and interactive:true.
-shell.options.useWizards=1;
-cluster.rescan({addInstances: "AUTO"});
-
-//@<> WL10644 - TSF2_4: Validate that the instances were added.
-validate_status(cluster.status(), [[__mysql_sandbox_port1, "OK"],
-                                   [__mysql_sandbox_port2, "OK"],
-                                   [__mysql_sandbox_port3, "OK"]]);
-
-//@<> Make sure that "addUnmanaged: true" behaves as addInstances "auto".
-session.runSql("DELETE FROM mysql_innodb_cluster_metadata.instances WHERE instance_name=?", [member_address2]);
-session.runSql("DELETE FROM mysql_innodb_cluster_metadata.instances WHERE instance_name=?", [member_address3]);
-
-validate_status(cluster.status(), [[__mysql_sandbox_port1, "RECOVERY_UNUSED_SINGLE"],
-                                   [__mysql_sandbox_port2, "UNMANAGED"],
-                                   [__mysql_sandbox_port3, "UNMANAGED"]]);
-
 cluster.rescan({addUnmanaged: true});
-EXPECT_OUTPUT_NOT_CONTAINS("The 'addInstances' and 'removeInstances' options are deprecated. Please use 'addUnmanaged' and/or 'removeObsolete' instead.");
 
 validate_status(cluster.status(), [[__mysql_sandbox_port1, "OK"],
                                    [__mysql_sandbox_port2, "OK"],
@@ -495,21 +293,6 @@ validate_status(cluster.status(), [[__mysql_sandbox_port1, "OK"],
 
 shell.options.useWizards=0;
 
-//@<> WL10644 - TSF2_5: Remove instances on port 2 and 3 from MD again.
-session.runSql("DELETE FROM mysql_innodb_cluster_metadata.instances WHERE instance_name=?", [member_address2]);
-session.runSql("DELETE FROM mysql_innodb_cluster_metadata.instances WHERE instance_name=?", [member_address3]);
-validate_status(cluster.status(), [[__mysql_sandbox_port1, "RECOVERY_UNUSED_SINGLE"],
-                                   [__mysql_sandbox_port2, "UNMANAGED"],
-                                   [__mysql_sandbox_port2, "UNMANAGED"]]);
-
-//@<> WL10644 - TSF2_5: Rescan with addInstances:"auto" and interactive:false.
-cluster.rescan({addInstances: "auto"});
-
-//@<> WL10644 - TSF2_5: Validate that the instances were added.
-validate_status(cluster.status(), [[__mysql_sandbox_port1, "OK"],
-                                   [__mysql_sandbox_port2, "OK"],
-                                   [__mysql_sandbox_port3, "OK"]]);
-
 //@<> WL10644 - TSF3_1: Disable GR in persisted settings {VER(>=8.0.11)}.
 //NOTE: GR configurations are not updated on my.cnf therefore GR settings
 //      are lost when the server is stopped.
@@ -534,8 +317,8 @@ validate_status(cluster.status(), [[__mysql_sandbox_port1, "OK"],
 //@<> WL10644 - TSF3_1: Number of instances in the MD before rescan().
 EXPECT_EQ(3, count_in_metadata_schema());
 
-//@<> WL10644 - TSF3_1: Rescan with removeInstances:[complete_valid_list].
-cluster.rescan({removeInstances: [member_address2, member_address3]});
+//@<> Rescan with removeObsolete: true.
+cluster.rescan({removeObsolete: true});
 
 //@<> WL10644 - TSF3_1: Number of instances in the MD after rescan().
 EXPECT_EQ(1, count_in_metadata_schema());
@@ -577,13 +360,13 @@ validate_status(cluster.status(), [[__mysql_sandbox_port1, "OK"],
 //@<> WL10644 - TSF3_2: Number of instances in the MD before rescan().
 EXPECT_EQ(3, count_in_metadata_schema());
 
-//@<> WL10644 - TSF3_2: Rescan with removeInstances:[incomplete_valid_list] and interactive:true.
+//@<> Rescan with interactive:true.
 shell.options.useWizards=1;
 
-testutil.expectPrompt("Would you like to remove it from the cluster metadata? [Y/n]:", "y");
-var member_fqdn_address2 = hostname + ":" + __mysql_sandbox_port2;
-var member_fqdn_address3 = hostname + ":" + __mysql_sandbox_port3;
-cluster.rescan({removeInstances: [member_fqdn_address2]});
+testutil.expectPrompt("Would you like to remove it from the cluster metadata? [Y/n]: ", "y");
+testutil.expectPrompt("Would you like to remove it from the cluster metadata? [Y/n]: ", "y");
+
+cluster.rescan();
 
 shell.options.useWizards=0;
 
@@ -627,45 +410,12 @@ validate_status(cluster.status(), [[__mysql_sandbox_port1, "OK"],
 //@<> WL10644 - TSF3_3: Number of instances in the MD before rescan().
 EXPECT_EQ(3, count_in_metadata_schema());
 
-//@<> WL10644 - TSF3_3: Rescan with removeInstances:[incomplete_valid_list] and interactive:false.
-cluster.rescan({removeInstances: [member_fqdn_address2]});
-
-//@<> WL10644 - TSF3_3: Number of instances in the MD after rescan().
-EXPECT_EQ(2, count_in_metadata_schema());
-
-//@<> WL10644 - TSF3_3: Validate that the instances were removed.
-validate_status(cluster.status(), [[__mysql_sandbox_port1, "OK"],
-                                   [__mysql_sandbox_port2, "N/A"],
-                                   [__mysql_sandbox_port3, "MISSING"]]);
-
-//@<> WL10644 - TSF3_4: Start instance on port 2 and add it back to the cluster.
-//NOTE: Not need for instance 3 (no removed from MD in previous test).
-testutil.startSandbox(__mysql_sandbox_port2);
-cluster.addInstance(__hostname_uri2);
-testutil.waitMemberState(__mysql_sandbox_port2, "ONLINE");
-
-//@<> WL10644 - TSF3_4: Disable GR in persisted settings {VER(>=8.0.11)}.
-//NOTE: GR configurations are not updated on my.cnf therefore GR settings
-//      are lost when the server is stopped.
-var s2 = mysql.getSession(__sandbox_uri2);
-s2.runSql("RESET PERSIST IF EXISTS group_replication_start_on_boot");
-s2.runSql("RESET PERSIST IF EXISTS group_replication_group_name");
-s2.close();
-
-//@<> WL10644 - TSF3_4: Stop instances on port 2 (no metadata MD changes).
-//NOTE: Not need for instance 3 (no removed from MD in previous test).
-testutil.stopSandbox(__mysql_sandbox_port2);
-testutil.waitMemberState(__mysql_sandbox_port2, "(MISSING)");
-validate_status(cluster.status(), [[__mysql_sandbox_port1, "OK"],
-                                   [__mysql_sandbox_port2, "MISSING"],
-                                   [__mysql_sandbox_port3, "MISSING"]]);
-
-//@<> WL10644 - TSF3_4: Number of instances in the MD before rescan().
-EXPECT_EQ(3, count_in_metadata_schema());
+//@<> Rescan with removeObsolete: true and interactive:true.
+var member_fqdn_address2 = hostname + ":" + __mysql_sandbox_port2;
+var member_fqdn_address3 = hostname + ":" + __mysql_sandbox_port3;
 
-//@<> WL10644 - TSF3_4: Rescan with removeInstances:"auto" and interactive:true.
 shell.options.useWizards=1;
-cluster.rescan({removeInstances: "auto"});
+cluster.rescan({removeObsolete: true});
 shell.options.useWizards=0;
 
 //@<> WL10644 - TSF3_4: Number of instances in the MD after rescan().
@@ -705,8 +455,6 @@ shell.options.useWizards=1;
 cluster.rescan({removeObsolete: true});
 shell.options.useWizards=0;
 
-EXPECT_OUTPUT_NOT_CONTAINS("The 'addInstances' and 'removeInstances' options are deprecated. Please use 'addUnmanaged' and/or 'removeObsolete' instead.");
-
 EXPECT_EQ(1, count_in_metadata_schema());
 validate_status(cluster.status(), [[__mysql_sandbox_port1, "OK"],
                                    [__mysql_sandbox_port2, "N/A"],
@@ -745,8 +493,8 @@ validate_status(cluster.status(), [[__mysql_sandbox_port1, "OK"],
 //@<> WL10644 - TSF3_5: Number of instances in the MD before rescan().
 EXPECT_EQ(3, count_in_metadata_schema());
 
-//@<> WL10644 - TSF3_5: Rescan with removeInstances:"auto" and interactive:false.
-cluster.rescan({removeInstances: "AUTO"});
+//@<> Rescan with removeObsolete: true and interactive:false.
+cluster.rescan({removeObsolete: true});
 
 //@<> WL10644 - TSF3_5: Number of instances in the MD after rescan().
 EXPECT_EQ(1, count_in_metadata_schema());
@@ -1024,7 +772,7 @@ EXPECT_OUTPUT_CONTAINS(`Dropping unused recovery account: 'mysql_innodb_cluster_
 status = cluster.status();
 EXPECT_FALSE(check_status_instance_error_msg(status, __mysql_sandbox_port1, `WARNING: Detected an unused recovery account: mysql_innodb_cluster_${instance2_id}. Use Cluster.rescan() to clean up.`));
 
-EXPECT_NO_THROWS(function () { cluster.rescan({addInstances: "auto"}) });
+EXPECT_NO_THROWS(function () { cluster.rescan({addUnmanaged: true}) });
 EXPECT_OUTPUT_CONTAINS(`The instance '${hostname}:${__mysql_sandbox_port2}' was successfully added to the cluster metadata.`);
 
 //@<> Bug #33235502 Check if recreated recovery account has the correct host
diff --git a/unittest/scripts/auto/js_adminapi/scripts/cluster_status.js b/unittest/scripts/auto/js_adminapi/scripts/cluster_status.js
index c2c4c69540..1fbf456c16 100644
--- a/unittest/scripts/auto/js_adminapi/scripts/cluster_status.js
+++ b/unittest/scripts/auto/js_adminapi/scripts/cluster_status.js
@@ -202,7 +202,7 @@ session1.runSql("create schema foobar");
 testutil.waitMemberState(__mysql_sandbox_port2, "(MISSING)");
 cluster.status();
 
-shell.options["dba.logSql"] = 1;
+shell.options["logSql"] = "on";
 WIPE_SHELL_LOG();
 
 cluster.status({extended:1});
diff --git a/unittest/scripts/auto/js_adminapi/scripts/dba_addinstance_crash.js b/unittest/scripts/auto/js_adminapi/scripts/dba_addinstance_crash.js
index 5f3bf34590..4b2cd4a5af 100644
--- a/unittest/scripts/auto/js_adminapi/scripts/dba_addinstance_crash.js
+++ b/unittest/scripts/auto/js_adminapi/scripts/dba_addinstance_crash.js
@@ -9,7 +9,7 @@ testutil.deploySandbox(__mysql_sandbox_port2, "root")
 
 shell.connect(__sandbox_uri1)
 
-shell.options["dba.logSql"]=1;
+shell.options["logSql"]="on";
 
 var cluster;
 
diff --git a/unittest/scripts/auto/js_adminapi/scripts/dba_adopt_from_gr.js b/unittest/scripts/auto/js_adminapi/scripts/dba_adopt_from_gr.js
index 7fab0ac6d7..318921ef3a 100644
--- a/unittest/scripts/auto/js_adminapi/scripts/dba_adopt_from_gr.js
+++ b/unittest/scripts/auto/js_adminapi/scripts/dba_adopt_from_gr.js
@@ -60,8 +60,8 @@ EXPECT_FALSE("instanceErrors" in status["defaultReplicaSet"]["topology"][`${host
 EXPECT_EQ(2, session.runSql("SELECT count(*) FROM mysql_innodb_cluster_metadata.instances WHERE attributes->>'$.recoveryAccountUser' REGEXP '^mysql_innodb_cluster_[[:digit:]]+$'").fetchOne()[0]);
 
 // Verify read_only_targets is set to secondary as default in the metadata
-routing_options = cluster.routingOptions();
-EXPECT_EQ("secondaries", routing_options["global"]["read_only_targets"]);
+router_options = cluster.routerOptions();
+EXPECT_EQ("secondaries", router_options["configuration"]["routing_rules"]["read_only_targets"]);
 
 testutil.waitMemberTransactions(__mysql_sandbox_port2);
 
diff --git a/unittest/scripts/auto/js_adminapi/scripts/dba_cluster_help.js b/unittest/scripts/auto/js_adminapi/scripts/dba_cluster_help.js
index ccfd8c2fe6..929b47bdad 100644
--- a/unittest/scripts/auto/js_adminapi/scripts/dba_cluster_help.js
+++ b/unittest/scripts/auto/js_adminapi/scripts/dba_cluster_help.js
@@ -224,15 +224,6 @@ cluster.help("addReplicaInstance")
 //@ addReplicaInstance, \help [USE:addReplicaInstance]
 \help cluster.addReplicaInstance
 
-//@ routingOptions
-cluster.help("routingOptions")
-
-//@ routingOptions, \? [USE:routingOptions]
-\? cluster.routingOptions
-
-//@ routingOptions, \help [USE:routingOptions]
-\help cluster.routingOptions
-
 //@ routerOptions
 cluster.help("routerOptions")
 
diff --git a/unittest/scripts/auto/js_adminapi/scripts/dba_cluster_interactive.js b/unittest/scripts/auto/js_adminapi/scripts/dba_cluster_interactive.js
index e7e831c224..1b802b9ff8 100644
--- a/unittest/scripts/auto/js_adminapi/scripts/dba_cluster_interactive.js
+++ b/unittest/scripts/auto/js_adminapi/scripts/dba_cluster_interactive.js
@@ -56,7 +56,6 @@ validateMembers(Cluster, [
   'fenceWrites',
   'unfenceWrites',
   'addReplicaInstance',
-  'routingOptions',
   'setRoutingOption',
   'routerOptions',
   'createRoutingGuideline',
diff --git a/unittest/scripts/auto/js_adminapi/scripts/dba_cluster_no_interactive.js b/unittest/scripts/auto/js_adminapi/scripts/dba_cluster_no_interactive.js
index 6b14774b73..df4180cdcd 100644
--- a/unittest/scripts/auto/js_adminapi/scripts/dba_cluster_no_interactive.js
+++ b/unittest/scripts/auto/js_adminapi/scripts/dba_cluster_no_interactive.js
@@ -45,7 +45,6 @@ validateMembers(Cluster, [
   'fenceWrites',
   'unfenceWrites',
   'addReplicaInstance',
-  'routingOptions',
   'setRoutingOption',
   'routerOptions',
   'createRoutingGuideline',
diff --git a/unittest/scripts/auto/js_adminapi/scripts/dba_cluster_rejoin_instance.js b/unittest/scripts/auto/js_adminapi/scripts/dba_cluster_rejoin_instance.js
index fe5da2da2e..a10682f2b6 100644
--- a/unittest/scripts/auto/js_adminapi/scripts/dba_cluster_rejoin_instance.js
+++ b/unittest/scripts/auto/js_adminapi/scripts/dba_cluster_rejoin_instance.js
@@ -1,6 +1,6 @@
 // Assumptions: smart deployment rountines available
 //@<> Initialization
-begin_dba_log_sql(2);
+\option logSql = unfiltered
 testutil.deploySandbox(__mysql_sandbox_port1, "root", {report_host: hostname});
 testutil.deploySandbox(__mysql_sandbox_port2, "root", {report_host: hostname});
 testutil.deploySandbox(__mysql_sandbox_port3, "root", {report_host: hostname});
@@ -112,10 +112,7 @@ session.runSql('SET sql_log_bin=1');
 session.close();
 
 //@<> Finalization
-var logs = end_dba_log_sql();
-EXPECT_NO_SQL(__sandbox1, logs);
-EXPECT_NO_SQL(__sandbox2, logs);
-EXPECT_NO_SQL(__sandbox3, logs);
+\option --unset logSql
 testutil.destroySandbox(__mysql_sandbox_port1);
 testutil.destroySandbox(__mysql_sandbox_port2);
 testutil.destroySandbox(__mysql_sandbox_port3);
diff --git a/unittest/scripts/auto/js_adminapi/scripts/dba_createcluster_crash.js b/unittest/scripts/auto/js_adminapi/scripts/dba_createcluster_crash.js
index 3681759b9f..a564982aa3 100644
--- a/unittest/scripts/auto/js_adminapi/scripts/dba_createcluster_crash.js
+++ b/unittest/scripts/auto/js_adminapi/scripts/dba_createcluster_crash.js
@@ -8,7 +8,7 @@ testutil.deploySandbox(__mysql_sandbox_port1, "root")
 
 shell.connect(__sandbox_uri1)
 
-shell.options["dba.logSql"]=1;
+shell.options["logSql"]="on";
 
 //@<> run createCluster in a loop, fail during it and see if we can recover
 
diff --git a/unittest/scripts/auto/js_adminapi/scripts/dba_preconditions_more.js b/unittest/scripts/auto/js_adminapi/scripts/dba_preconditions_more.js
index 8202c47c86..46bc36aad7 100644
--- a/unittest/scripts/auto/js_adminapi/scripts/dba_preconditions_more.js
+++ b/unittest/scripts/auto/js_adminapi/scripts/dba_preconditions_more.js
@@ -285,7 +285,6 @@ check_exception(function(){ cluster.forceQuorumUsingPartitionOf(__sandbox_uri1);
 check_exception(function(){ cluster.switchToSinglePrimaryMode(); }, expect_exp, expect_output);
 check_exception(function(){ cluster.switchToMultiPrimaryMode(); }, expect_exp, expect_output);
 check_exception(function(){ cluster.setRoutingOption("tag:test_tag", 567); }, expect_exp, expect_output);
-check_exception(function(){ cluster.routingOptions(); }, expect_exp, expect_output);
 check_exception(function(){ cluster.fenceAllTraffic(); }, expect_exp, expect_output);
 check_exception(function(){ cluster.fenceWrites(); }, expect_exp, expect_output);
 check_exception(function(){ cluster.unfenceWrites(); }, expect_exp, expect_output);
diff --git a/unittest/scripts/auto/js_adminapi/scripts/deprecations.js b/unittest/scripts/auto/js_adminapi/scripts/deprecations.js
deleted file mode 100644
index cfc5c6ec23..0000000000
--- a/unittest/scripts/auto/js_adminapi/scripts/deprecations.js
+++ /dev/null
@@ -1,48 +0,0 @@
-//@{!__dbug_off}
-
-//@<> Setup
-var scene = new ClusterScenario([__mysql_sandbox_port1]);
-testutil.deploySandbox(__mysql_sandbox_port2, "root");
-var cluster = scene.cluster;
-shell.connect(__sandbox_uri1);
-
-//@<> A deprecation warning must be emitted when using the command cluster.rescan() with "addInstances" and/or "removeInstances" options
-EXPECT_NO_THROWS(function() { cluster.rescan({addInstances: "auto"}); });
-EXPECT_OUTPUT_CONTAINS(`The 'addInstances' and 'removeInstances' options are deprecated. Please use 'addUnmanaged' and/or 'removeObsolete' instead.`);
-WIPE_OUTPUT();
-
-EXPECT_NO_THROWS(function() { cluster.rescan({removeInstances: "auto"}); });
-EXPECT_OUTPUT_CONTAINS(`The 'addInstances' and 'removeInstances' options are deprecated. Please use 'addUnmanaged' and/or 'removeObsolete' instead.`);
-WIPE_OUTPUT();
-
-EXPECT_NO_THROWS(function() { cluster.rescan({addInstances: "auto", removeInstances: "auto"}); });
-EXPECT_OUTPUT_CONTAINS(`The 'addInstances' and 'removeInstances' options are deprecated. Please use 'addUnmanaged' and/or 'removeObsolete' instead.`);
-WIPE_OUTPUT();
-
-//@<> A deprecation warning must be emitted when using the command cluster.routingOptions()
-EXPECT_NO_THROWS(function() { cluster.routingOptions(); });
-EXPECT_OUTPUT_CONTAINS(`WARNING: This function is deprecated and will be removed in a future release of MySQL Shell. Use <Cluster>.routerOptions() instead.`);
-WIPE_OUTPUT();
-
-//@<> A deprecation warning must be emitted when using the command replicaset.routingOptions()
-EXPECT_NO_THROWS(function() {
-  cluster.dissolve();
-  rs = dba.createReplicaSet("rs");
-  rs.routingOptions();
-});
-EXPECT_OUTPUT_CONTAINS(`WARNING: This function is deprecated and will be removed in a future release of MySQL Shell. Use <ReplicaSet>.routerOptions() instead.`);
-WIPE_OUTPUT();
-
-//@<> A deprecation warning must be emitted when using the command clusterset.routingOptions()
-EXPECT_NO_THROWS(function() {
-  rs.dissolve();
-  cluster = dba.createCluster("cluster");
-  cs = cluster.createClusterSet("cs");
-  cs.routingOptions();
-});
-EXPECT_OUTPUT_CONTAINS(`WARNING: This function is deprecated and will be removed in a future release of MySQL Shell. Use <ClusterSet>.routerOptions() instead.`);
-WIPE_OUTPUT();
-
-//@<> Cleanup
-scene.destroy();
-testutil.destroySandbox(__mysql_sandbox_port2);
diff --git a/unittest/scripts/auto/js_adminapi/scripts/locking_norecord.js b/unittest/scripts/auto/js_adminapi/scripts/locking_norecord.js
index b27eb7f125..6d3915369c 100644
--- a/unittest/scripts/auto/js_adminapi/scripts/locking_norecord.js
+++ b/unittest/scripts/auto/js_adminapi/scripts/locking_norecord.js
@@ -102,10 +102,10 @@ cluster_lock_check(function() {
 
 //@<> exclusive lock on cluster.rescan()
 cluster_lock_check(function() {
-    cluster.rescan({addInstances: [`${hostname}:${__mysql_sandbox_port1}`]});
+    cluster.rescan({addUnmanaged: true});
 });
 cluster_lock_check(function() {
-    cluster2.rescan({addInstances: [`${hostname}:${__mysql_sandbox_port1}`]});
+    cluster2.rescan({addUnmanaged: true});
 });
 
 //@<> exclusive lock on dba.upgradeMetadata()
diff --git a/unittest/scripts/auto/js_adminapi/scripts/log_sql.js b/unittest/scripts/auto/js_adminapi/scripts/log_sql.js
index b6700fe1c2..7a9fce08ce 100644
--- a/unittest/scripts/auto/js_adminapi/scripts/log_sql.js
+++ b/unittest/scripts/auto/js_adminapi/scripts/log_sql.js
@@ -145,9 +145,9 @@ var drop_metadata_sql = [
     "DROP SCHEMA IF EXISTS `mysql_innodb_cluster_metadata`"
 ];
 
-// ---------- WL#13294 (dba.logSql = 0) ----------
+// ---------- WL#13294 (logSql = off)(replaced dba.logSql = 0) ----------
 
-//@<> WL#13294: Initialization (dba.logSql = 0).
+//@<> WL#13294: Initialization (logSql = off).
 testutil.deploySandbox(__mysql_sandbox_port1, 'root', {report_host: hostname}, {createRemoteRoot:true});
 testutil.snapshotSandboxConf(__mysql_sandbox_port1);
 testutil.deploySandbox(__mysql_sandbox_port2, "root", {report_host: hostname});
@@ -160,15 +160,15 @@ var mycnf3 = testutil.getSandboxConfPath(__mysql_sandbox_port3);
 
 shell.connect(__sandbox_uri1);
 
-//@<> WL#13294: set dba.logSql = 0.
-\option dba.logSql = 0
+//@<> WL#13294: set logSql = off
+\option logSql = off
 
-//@<> WL#13294: set some configuration incorrectly (that can be fixed without restart) (dba.logSql = 0).
+//@<> WL#13294: set some configuration incorrectly (that can be fixed without restart) (logSql = off).
 session.runSql("SET sql_log_bin=0");
 session.runSql("SET GLOBAL binlog_format = STATEMENT");
 session.runSql("SET sql_log_bin=1");
 
-//@<> WL#13294: check instance (dba.logSql = 0).
+//@<> WL#13294: check instance (logSql = off).
 WIPE_SHELL_LOG();
 dba.checkInstanceConfiguration(__sandbox_uri1, {mycnfPath: mycnf1});
 EXPECT_SHELL_LOG_NOT_CONTAINS(check_instance_sql[0]);
@@ -176,25 +176,25 @@ EXPECT_SHELL_LOG_NOT_CONTAINS(check_instance_sql[1]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(check_instance_sql[2]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(check_instance_sql[3]);
 
-//@<> WL#13294: configure instance (dba.logSql = 0).
+//@<> WL#13294: configure instance (logSql = off).
 WIPE_SHELL_LOG();
 dba.configureInstance(__sandbox_uri1, {mycnfPath: mycnf1});
 EXPECT_SHELL_LOG_NOT_CONTAINS(configure_instance_sql[0]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(configure_instance_sql[1]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(configure_instance_sql[2]);
 
-//@<> WL#13294: create cluster (dba.logSql = 0).
+//@<> WL#13294: create cluster (logSql = off).
 WIPE_SHELL_LOG();
 var c = dba.createCluster('test', {gtidSetIsComplete: true});
 EXPECT_SHELL_LOG_NOT_CONTAINS(create_cluster_sql[0]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(create_cluster_sql[1]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(create_cluster_sql[2]);
-//@<> WL#13294: create cluster (dba.logSql = 0). < 8.0.23 {VER(<8.0.23)}
+//@<> WL#13294: create cluster (logSql = off). < 8.0.23 {VER(<8.0.23)}
 EXPECT_SHELL_LOG_NOT_CONTAINS(create_cluster_sql[3]);
-//@<> WL#13294: create cluster (dba.logSql = 0). >= 8.0.23 {VER(>=8.0.23)}
+//@<> WL#13294: create cluster (logSql = off). >= 8.0.23 {VER(>=8.0.23)}
 EXPECT_SHELL_LOG_NOT_CONTAINS(create_cluster_sql[4]);
 
-//@<> WL#13294: add instance (dba.logSql = 0).
+//@<> WL#13294: add instance (logSql = off).
 WIPE_SHELL_LOG();
 c.addInstance(__sandbox_uri2);
 EXPECT_SHELL_LOG_NOT_CONTAINS(add_instance_sql[0]);
@@ -204,34 +204,34 @@ EXPECT_SHELL_LOG_NOT_CONTAINS(add_instance_sql[3]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(add_instance_sql[4]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(add_instance_sql[5]);
 
-//@<> WL#13294: add another instance (dba.logSql = 0).
+//@<> WL#13294: add another instance (logSql = off).
 c.addInstance(__sandbox_uri3);
 
-//@<> WL#13294: persist GR configuration settings for 5.7 servers (dba.logSql = 0). {VER(<8.0.11)}
+//@<> WL#13294: persist GR configuration settings for 5.7 servers (logSql = off). {VER(<8.0.11)}
 dba.configureInstance('root:root@localhost:' + __mysql_sandbox_port1, {mycnfPath: mycnf1});
 dba.configureInstance('root:root@localhost:' + __mysql_sandbox_port2, {mycnfPath: mycnf2});
 dba.configureInstance('root:root@localhost:' + __mysql_sandbox_port3, {mycnfPath: mycnf3});
 
-//@<> WL#13294: Stop GR on an instance to use rejoin instance (dba.logSql = 0).
+//@<> WL#13294: Stop GR on an instance to use rejoin instance (logSql = off).
 shell.connect(__sandbox_uri3);
 session.runSql("STOP GROUP_REPLICATION");
 session.close();
 shell.connect(__sandbox_uri1);
 
-//@<> WL#13294: rejoin instance (dba.logSql = 0).
+//@<> WL#13294: rejoin instance (logSql = off).
 WIPE_SHELL_LOG();
 c.rejoinInstance(__sandbox_uri3);
 EXPECT_SHELL_LOG_NOT_CONTAINS(rejoin_instance_sql[0]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(rejoin_instance_sql[1]);
 testutil.waitMemberState(__mysql_sandbox_port3, "ONLINE");
 
-//@<> WL#13294: get cluster (dba.logSql = 0).
+//@<> WL#13294: get cluster (logSql = off).
 WIPE_SHELL_LOG();
 var c = dba.getCluster();
 EXPECT_SHELL_LOG_NOT_CONTAINS(get_cluster_sql[0]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(get_cluster_sql[1]);
 
-//@<> WL#13294: status (dba.logSql = 0).
+//@<> WL#13294: status (logSql = off).
 WIPE_SHELL_LOG();
 c.status();
 EXPECT_SHELL_LOG_NOT_CONTAINS(status_sql[0]);
@@ -239,93 +239,93 @@ EXPECT_SHELL_LOG_NOT_CONTAINS(status_sql[1]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(status_sql[2]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(status_sql[3]);
 
-//@<> WL#13294: describe (dba.logSql = 0).
+//@<> WL#13294: describe (logSql = off).
 WIPE_SHELL_LOG();
 c.describe();
 EXPECT_SHELL_LOG_NOT_CONTAINS(describe_sql[0]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(describe_sql[1]);
 
-//@<> WL#13294: set option (dba.logSql = 0).
+//@<> WL#13294: set option (logSql = off).
 WIPE_SHELL_LOG();
 c.setOption("clusterName", "test_cluster");
 EXPECT_SHELL_LOG_NOT_CONTAINS(set_option_sql[0]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(set_option_sql[1]);
 
-//@<> WL#13294: set instance option (dba.logSql = 0).
+//@<> WL#13294: set instance option (logSql = off).
 WIPE_SHELL_LOG();
 c.setInstanceOption(__sandbox_uri3, "label", "instance_3_label");
 EXPECT_SHELL_LOG_NOT_CONTAINS(set_instance_option_sql[0]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(set_instance_option_sql[1]);
 
-//@<> WL#13294: options (dba.logSql = 0).
+//@<> WL#13294: options (logSql = off).
 WIPE_SHELL_LOG();
 c.options();
 EXPECT_SHELL_LOG_NOT_CONTAINS(options_sql[0]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(options_sql[1]);
 
-//@<> WL#13294: remove metadata from one instance for rescan() to add it (dba.logSql = 0).
+//@<> WL#13294: remove metadata from one instance for rescan() to add it (logSql = off).
 session.runSql("DELETE FROM mysql_innodb_cluster_metadata.instances WHERE instance_name=?", ["instance_3_label"]);
 
-//@<> WL#13294: rescan (dba.logSql = 0).
+//@<> WL#13294: rescan (logSql = off).
 WIPE_SHELL_LOG();
-c.rescan({addInstances: "AUTO"});
+c.rescan({addUnmanaged: "true"});
 EXPECT_SHELL_LOG_NOT_CONTAINS(rescan_sql[0]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(rescan_sql[1]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(rescan_sql[2]);
 
-//@<> WL#13294: set primary instance (dba.logSql = 0). {VER(>=8.0.13)}
+//@<> WL#13294: set primary instance (logSql = off). {VER(>=8.0.13)}
 WIPE_SHELL_LOG();
 c.setPrimaryInstance(__sandbox_uri2);
 EXPECT_SHELL_LOG_NOT_CONTAINS(set_primary_instance_sql[0]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(set_primary_instance_sql[1]);
 
-//@<> Bug#33538559: set primary instance with timeout (dba.logSql = 0). {VER(>=8.0.29)}
+//@<> Bug#33538559: set primary instance with timeout (logSql = off). {VER(>=8.0.29)}
 WIPE_SHELL_LOG();
 c.setPrimaryInstance(__sandbox_uri2, {runningTransactionsTimeout: 11});
 EXPECT_SHELL_LOG_NOT_CONTAINS(set_primary_instance_timeout_sql[0]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(set_primary_instance_timeout_sql[1]);
 
-//@<> WL#13294: switch to multi-primary (dba.logSql = 0). {VER(>=8.0.13)}
+//@<> WL#13294: switch to multi-primary (logSql = off). {VER(>=8.0.13)}
 WIPE_SHELL_LOG();
 c.switchToMultiPrimaryMode();
 EXPECT_SHELL_LOG_NOT_CONTAINS(switch_multi_primary_sql[0]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(switch_multi_primary_sql[1]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(switch_multi_primary_sql[2]);
 
-//@<> WL#13294: switch to single-primary (dba.logSql = 0). {VER(>=8.0.13)}
+//@<> WL#13294: switch to single-primary (logSql = off). {VER(>=8.0.13)}
 WIPE_SHELL_LOG();
 c.switchToSinglePrimaryMode(__sandbox_uri1);
 EXPECT_SHELL_LOG_NOT_CONTAINS(switch_single_primary_sql[0]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(switch_single_primary_sql[1]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(switch_single_primary_sql[2]);
 
-//@<> WL#13294: disable gr_start_on_boot on all instances to kill (dba.logSql = 0).
+//@<> WL#13294: disable gr_start_on_boot on all instances to kill (logSql = off).
 //disable_auto_rejoin(__mysql_sandbox_port1);
 disable_auto_rejoin(__mysql_sandbox_port2);
 disable_auto_rejoin(__mysql_sandbox_port3);
 shell.connect(__sandbox_uri1);
 
-//@<> WL#13294: lose quorum (dba.logSql = 0).
+//@<> WL#13294: lose quorum (logSql = off).
 testutil.killSandbox(__mysql_sandbox_port2);
 testutil.killSandbox(__mysql_sandbox_port3);
 testutil.waitMemberState(__mysql_sandbox_port2, "UNREACHABLE,(MISSING)");
 testutil.waitMemberState(__mysql_sandbox_port3, "UNREACHABLE,(MISSING)");
 
-//@<> WL#13294: force quorum (dba.logSql = 0).
+//@<> WL#13294: force quorum (logSql = off).
 WIPE_SHELL_LOG();
 c.forceQuorumUsingPartitionOf(__sandbox_uri1);
 EXPECT_SHELL_LOG_NOT_CONTAINS(force_quorum_sql[0]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(force_quorum_sql[1]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(force_quorum_sql[2]);
 
-//@<> WL#13294: restart killed instances (dba.logSql = 0).
+//@<> WL#13294: restart killed instances (logSql = off).
 testutil.startSandbox(__mysql_sandbox_port2);
 testutil.startSandbox(__mysql_sandbox_port3);
 
-//@<> WL#13294: Stop GR on remaining cluster instance (dba.logSql = 0).
+//@<> WL#13294: Stop GR on remaining cluster instance (logSql = off).
 session.runSql("STOP GROUP_REPLICATION");
 
-//@<> WL#13294: reboot cluster (dba.logSql = 0).
+//@<> WL#13294: reboot cluster (logSql = off).
 WIPE_SHELL_LOG();
 c = dba.rebootClusterFromCompleteOutage("test_cluster");
 EXPECT_SHELL_LOG_NOT_CONTAINS(reboot_cluster_sql[0]);
@@ -336,7 +336,7 @@ EXPECT_SHELL_LOG_NOT_CONTAINS(reboot_cluster_sql[3]);
 testutil.waitMemberState(__mysql_sandbox_port2, "ONLINE");
 testutil.waitMemberState(__mysql_sandbox_port3, "ONLINE");
 
-//@<> WL#13294: remove instance (dba.logSql = 0).
+//@<> WL#13294: remove instance (logSql = off).
 WIPE_SHELL_LOG();
 c.removeInstance(__sandbox_uri2);
 EXPECT_SHELL_LOG_NOT_CONTAINS(remove_instance_sql[0]);
@@ -344,21 +344,21 @@ EXPECT_SHELL_LOG_NOT_CONTAINS(remove_instance_sql[1]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(remove_instance_sql[2]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(remove_instance_sql[3]);
 
-//@<> WL#13294: drop metadata (dba.logSql = 0).
+//@<> WL#13294: drop metadata (logSql = off).
 session.runSql("SET GLOBAL super_read_only = 'OFF'");
 WIPE_SHELL_LOG();
 dba.dropMetadataSchema({force: true});
 EXPECT_SHELL_LOG_NOT_CONTAINS(drop_metadata_sql[0]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(drop_metadata_sql[1]);
 
-//@<> createCluster(adoptFromGR) (dba.logSql = 0).
+//@<> createCluster(adoptFromGR) (logSql = off).
 WIPE_SHELL_LOG();
 c = dba.createCluster('test', {adoptFromGR: true});
 EXPECT_SHELL_LOG_NOT_CONTAINS(create_cluster_adopt_sql[0]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(create_cluster_adopt_sql[1]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(create_cluster_adopt_sql[2]);
 
-//@<> WL#13294: dissolve (dba.logSql = 0).
+//@<> WL#13294: dissolve (logSql = off).
 WIPE_SHELL_LOG();
 c.dissolve();
 EXPECT_SHELL_LOG_NOT_CONTAINS(dissolve_sql[0]);
@@ -366,8 +366,8 @@ EXPECT_SHELL_LOG_NOT_CONTAINS(dissolve_sql[1]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(dissolve_sql[2]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(dissolve_sql[3]);
 
-//@<> WL#13294: Clean-up (dba.logSql = 0).
-\option dba.logSql = 0
+//@<> WL#13294: Clean-up (logSql = off).
+\option logSql = off
 \option verbose = 0
 session.close();
 testutil.destroySandbox(__mysql_sandbox_port1);
@@ -375,9 +375,9 @@ testutil.destroySandbox(__mysql_sandbox_port2);
 testutil.destroySandbox(__mysql_sandbox_port3);
 
 
-// ---------- WL#13294 (dba.logSql = 1) ----------
+// ---------- WL#13294 (logSql = on) (replaced dba.logSql = 1) ----------
 
-//@<> WL#13294: Initialization (dba.logSql = 1).
+//@<> WL#13294: Initialization (logSql = on).
 testutil.deploySandbox(__mysql_sandbox_port1, 'root', {report_host: hostname}, {createRemoteRoot:true});
 testutil.snapshotSandboxConf(__mysql_sandbox_port1);
 testutil.deploySandbox(__mysql_sandbox_port2, "root", {report_host: hostname});
@@ -390,15 +390,15 @@ var mycnf3 = testutil.getSandboxConfPath(__mysql_sandbox_port3);
 
 shell.connect(__sandbox_uri1);
 
-//@<> WL#13294: set dba.logSql = 1.
-\option dba.logSql = 1
+//@<> WL#13294: set dlogSql = on.
+\option logSql = on
 
-//@<> WL#13294: set some configuration incorrectly (that can be fixed without restart) (dba.logSql = 1).
+//@<> WL#13294: set some configuration incorrectly (that can be fixed without restart) (logSql = on).
 session.runSql("SET sql_log_bin=0");
 session.runSql("SET GLOBAL binlog_format = STATEMENT");
 session.runSql("SET sql_log_bin=1");
 
-//@<> WL#13294: check instance (dba.logSql = 1).
+//@<> WL#13294: check instance (logSql = on).
 WIPE_SHELL_LOG();
 dba.checkInstanceConfiguration(__sandbox_uri1, {mycnfPath: mycnf1});
 EXPECT_SHELL_LOG_NOT_CONTAINS(check_instance_sql[0]);
@@ -406,26 +406,44 @@ EXPECT_SHELL_LOG_NOT_CONTAINS(check_instance_sql[1]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(check_instance_sql[2]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(check_instance_sql[3]);
 
-//@<> WL#13294: configure instance (dba.logSql = 1).
+//@<> WL#13294: configure instance (logSql = on).
 WIPE_SHELL_LOG();
 dba.configureInstance(__sandbox_uri1, {mycnfPath: mycnf1});
 EXPECT_SHELL_LOG_NOT_CONTAINS(configure_instance_sql[0]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(configure_instance_sql[1]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(configure_instance_sql[2]);
 
-//@<> WL#13294: create cluster (dba.logSql = 1).
+//@<> WL#13294: create cluster (logSql = on, dba-log-sql compatible).
 WIPE_SHELL_LOG();
+// option needed for showing entries with PASSWORD
+\option logSql.ignorePatternUnsafe=
 var c = dba.createCluster('test', {gtidSetIsComplete: true});
-EXPECT_SHELL_LOG_NOT_CONTAINS(create_cluster_sql[0]);
-EXPECT_SHELL_LOG_NOT_CONTAINS(create_cluster_sql[1]);
+//EXPECT_SHELL_LOG_NOT_CONTAINS(create_cluster_sql[0]);
+//EXPECT_SHELL_LOG_NOT_CONTAINS(create_cluster_sql[1]);
 EXPECT_SHELL_LOG_CONTAINS(create_cluster_sql[2]);
-//@<> WL#13294: create cluster (dba.logSql = 1). < 8.0.23 {VER(<8.0.23)}
+//@<> WL#13294: create cluster (logSql = on, dba-log-sql compatible). < 8.0.23 {VER(<8.0.23)}
 EXPECT_SHELL_LOG_CONTAINS(create_cluster_sql[3]);
-//@<> WL#13294: create cluster (dba.logSql = 1). >= 8.0.23 {VER(>=8.0.23)}
+//@<> WL#13294: create cluster (logSql = on, dba-log-sql compatible). >= 8.0.23 {VER(>=8.0.23)}
 EXPECT_SHELL_LOG_CONTAINS(create_cluster_sql[4]);
+\option --unset logSql.ignorePatternUnsafe
+c.dissolve();
+
+//@<> WL#13294: create cluster (logSql = on).
+WIPE_SHELL_LOG();
+var c = dba.createCluster('test', {gtidSetIsComplete: true});
+EXPECT_SHELL_LOG_NOT_CONTAINS(create_cluster_sql[0]);
+EXPECT_SHELL_LOG_NOT_CONTAINS(create_cluster_sql[1]);
+EXPECT_SHELL_LOG_CONTAINS(create_cluster_sql[2]);
+// two next entries contain "PASSWORD" keyword, and will be filtered out from the log
+//@<> WL#13294: create cluster (logSql = on). < 8.0.23 {VER(<8.0.23)}
+EXPECT_SHELL_LOG_NOT_CONTAINS(create_cluster_sql[3]);
+//@<> WL#13294: create cluster (logSql = on). >= 8.0.23 {VER(>=8.0.23)}
+EXPECT_SHELL_LOG_NOT_CONTAINS(create_cluster_sql[4]);
 
-//@<> WL#13294: add instance (dba.logSql = 1).
+//@<> WL#13294: add instance (logSql = on, dba-log-sql compatible).
 WIPE_SHELL_LOG();
+// option needed for compatibility with dba-log-sql
+\option logSql.ignorePatternUnsafe=
 c.addInstance(__sandbox_uri2);
 EXPECT_SHELL_LOG_NOT_CONTAINS(add_instance_sql[0]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(add_instance_sql[1]);
@@ -433,22 +451,34 @@ EXPECT_SHELL_LOG_NOT_CONTAINS(add_instance_sql[2]);
 EXPECT_SHELL_LOG_CONTAINS(add_instance_sql[3]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(add_instance_sql[4]);
 EXPECT_SHELL_LOG_CONTAINS(add_instance_sql[5]);
+\option --unset logSql.ignorePatternUnsafe
+c.removeInstance(__sandbox_uri2)
+
+//@<> WL#13294: add instance (logSql = on).
+WIPE_SHELL_LOG();
+c.addInstance(__sandbox_uri2);
+EXPECT_SHELL_LOG_NOT_CONTAINS(add_instance_sql[0]);
+EXPECT_SHELL_LOG_NOT_CONTAINS(add_instance_sql[1]);
+EXPECT_SHELL_LOG_NOT_CONTAINS(add_instance_sql[2]);
+EXPECT_SHELL_LOG_NOT_CONTAINS(add_instance_sql[3]);
+EXPECT_SHELL_LOG_NOT_CONTAINS(add_instance_sql[4]);
+EXPECT_SHELL_LOG_CONTAINS(add_instance_sql[5]);
 
-//@<> WL#13294: add another instance (dba.logSql = 1).
+//@<> WL#13294: add another instance (logSql = on).
 c.addInstance(__sandbox_uri3);
 
-//@<> WL#13294: persist GR configuration settings for 5.7 servers (dba.logSql = 1). {VER(<8.0.11)}
+//@<> WL#13294: persist GR configuration settings for 5.7 servers (logSql = on). {VER(<8.0.11)}
 dba.configureInstance('root:root@localhost:' + __mysql_sandbox_port1, {mycnfPath: mycnf1});
 dba.configureInstance('root:root@localhost:' + __mysql_sandbox_port2, {mycnfPath: mycnf2});
 dba.configureInstance('root:root@localhost:' + __mysql_sandbox_port3, {mycnfPath: mycnf3});
 
-//@<> WL#13294: Stop GR on an instance to use rejoin instance (dba.logSql = 1).
+//@<> WL#13294: Stop GR on an instance to use rejoin instance (logSql = on).
 shell.connect(__sandbox_uri3);
 session.runSql("STOP GROUP_REPLICATION");
 session.close();
 shell.connect(__sandbox_uri1);
 
-//@<> WL#13294: rejoin instance (dba.logSql = 1).
+//@<> WL#13294: rejoin instance (logSql = on).
 WIPE_SHELL_LOG();
 c.rejoinInstance(__sandbox_uri3);
 EXPECT_SHELL_LOG_NOT_CONTAINS(rejoin_instance_sql[0]);
@@ -456,13 +486,13 @@ EXPECT_SHELL_LOG_CONTAINS(rejoin_instance_sql[1]);
 
 testutil.waitMemberState(__mysql_sandbox_port3, "ONLINE");
 
-//@<> WL#13294: get cluster (dba.logSql = 1).
+//@<> WL#13294: get cluster (logSql = on).
 WIPE_SHELL_LOG();
 var c = dba.getCluster();
 EXPECT_SHELL_LOG_NOT_CONTAINS(get_cluster_sql[0]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(get_cluster_sql[1]);
 
-//@<> WL#13294: status (dba.logSql = 1).
+//@<> WL#13294: status (logSql = on).
 WIPE_SHELL_LOG();
 c.status();
 EXPECT_SHELL_LOG_NOT_CONTAINS(status_sql[0]);
@@ -470,93 +500,93 @@ EXPECT_SHELL_LOG_NOT_CONTAINS(status_sql[1]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(status_sql[2]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(status_sql[3]);
 
-//@<> WL#13294: describe (dba.logSql = 1).
+//@<> WL#13294: describe (logSql = on).
 WIPE_SHELL_LOG();
 c.describe();
 EXPECT_SHELL_LOG_NOT_CONTAINS(describe_sql[0]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(describe_sql[1]);
 
-//@<> WL#13294: set option (dba.logSql = 1).
+//@<> WL#13294: set option (logSql = on).
 WIPE_SHELL_LOG();
 c.setOption("clusterName", "test_cluster");
 EXPECT_SHELL_LOG_NOT_CONTAINS(set_option_sql[0]);
 EXPECT_SHELL_LOG_CONTAINS(set_option_sql[1]);
 
-//@<> WL#13294: set instance option (dba.logSql = 1).
+//@<> WL#13294: set instance option (logSql = on).
 WIPE_SHELL_LOG();
 c.setInstanceOption(__sandbox_uri3, "label", "instance_3_label");
 EXPECT_SHELL_LOG_NOT_CONTAINS(set_instance_option_sql[0]);
 EXPECT_SHELL_LOG_CONTAINS(set_instance_option_sql[1]);
 
-//@<> WL#13294: options (dba.logSql = 1).
+//@<> WL#13294: options (logSql = on).
 WIPE_SHELL_LOG();
 c.options();
 EXPECT_SHELL_LOG_NOT_CONTAINS(options_sql[0]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(options_sql[1]);
 
-//@<> WL#13294: remove metadata from one instance for rescan() to add it (dba.logSql = 1).
+//@<> WL#13294: remove metadata from one instance for rescan() to add it (logSql = on).
 session.runSql("DELETE FROM mysql_innodb_cluster_metadata.instances WHERE instance_name=?", ["instance_3_label"]);
 
-//@<> WL#13294: rescan (dba.logSql = 1).
+//@<> WL#13294: rescan (logSql = on).
 WIPE_SHELL_LOG();
-c.rescan({addInstances: "AUTO"});
+c.rescan({addUnmanaged: "true"});
 EXPECT_SHELL_LOG_NOT_CONTAINS(rescan_sql[0]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(rescan_sql[1]);
 EXPECT_SHELL_LOG_CONTAINS(rescan_sql[2]);
 
-//@<> WL#13294: set primary instance (dba.logSql = 1). {VER(>=8.0.13)}
+//@<> WL#13294: set primary instance (logSql = on). {VER(>=8.0.13)}
 WIPE_SHELL_LOG();
 c.setPrimaryInstance(__sandbox_uri2);
 EXPECT_SHELL_LOG_NOT_CONTAINS(set_primary_instance_sql[0]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(set_primary_instance_sql[1]);
 
-//@<> Bug#33538559: set primary instance with timeout (dba.logSql = 1). {VER(>=8.0.29)}
+//@<> Bug#33538559: set primary instance with timeout (logSql = on). {VER(>=8.0.29)}
 WIPE_SHELL_LOG();
 c.setPrimaryInstance(__sandbox_uri2, {runningTransactionsTimeout: 11});
 EXPECT_SHELL_LOG_NOT_CONTAINS(set_primary_instance_timeout_sql[0]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(set_primary_instance_timeout_sql[1]);
 
-//@<> WL#13294: switch to multi-primary (dba.logSql = 1). {VER(>=8.0.13)}
+//@<> WL#13294: switch to multi-primary (logSql = on). {VER(>=8.0.13)}
 WIPE_SHELL_LOG();
 c.switchToMultiPrimaryMode();
 EXPECT_SHELL_LOG_NOT_CONTAINS(switch_multi_primary_sql[0]);
 EXPECT_SHELL_LOG_CONTAINS(switch_multi_primary_sql[1]);
 EXPECT_SHELL_LOG_CONTAINS(switch_multi_primary_sql[2]);
 
-//@<> WL#13294: switch to single-primary (dba.logSql = 1). {VER(>=8.0.13)}
+//@<> WL#13294: switch to single-primary (logSql = on). {VER(>=8.0.13)}
 WIPE_SHELL_LOG();
 c.switchToSinglePrimaryMode(__sandbox_uri1);
 EXPECT_SHELL_LOG_NOT_CONTAINS(switch_single_primary_sql[0]);
 EXPECT_SHELL_LOG_CONTAINS(switch_single_primary_sql[1]);
 EXPECT_SHELL_LOG_CONTAINS(switch_single_primary_sql[2]);
 
-//@<> WL#13294: disable gr_start_on_boot on all instances to kill (dba.logSql = 1).
+//@<> WL#13294: disable gr_start_on_boot on all instances to kill (logSql = on).
 //disable_auto_rejoin(__mysql_sandbox_port1);
 disable_auto_rejoin(__mysql_sandbox_port2);
 disable_auto_rejoin(__mysql_sandbox_port3);
 shell.connect(__sandbox_uri1);
 
-//@<> WL#13294: lose quorum (dba.logSql = 1).
+//@<> WL#13294: lose quorum (logSql = on).
 testutil.killSandbox(__mysql_sandbox_port2);
 testutil.killSandbox(__mysql_sandbox_port3);
 testutil.waitMemberState(__mysql_sandbox_port2, "UNREACHABLE,(MISSING)");
 testutil.waitMemberState(__mysql_sandbox_port3, "UNREACHABLE,(MISSING)");
 
-//@<> WL#13294: force quorum (dba.logSql = 1).
+//@<> WL#13294: force quorum (logSql = on).
 WIPE_SHELL_LOG();
 c.forceQuorumUsingPartitionOf(__sandbox_uri1);
 EXPECT_SHELL_LOG_NOT_CONTAINS(force_quorum_sql[0]);
 EXPECT_SHELL_LOG_CONTAINS(force_quorum_sql[1]);
 EXPECT_SHELL_LOG_CONTAINS(force_quorum_sql[2]);
 
-//@<> WL#13294: restart killed instances (dba.logSql = 1).
+//@<> WL#13294: restart killed instances (logSql = on).
 testutil.startSandbox(__mysql_sandbox_port2);
 testutil.startSandbox(__mysql_sandbox_port3);
 
-//@<> WL#13294: Stop GR on remaining cluster instance (dba.logSql = 1).
+//@<> WL#13294: Stop GR on remaining cluster instance (logSql = on).
 session.runSql("STOP GROUP_REPLICATION");
 
-//@<> WL#13294: reboot cluster (dba.logSql = 1).
+//@<> WL#13294: reboot cluster (logSql = on).
 WIPE_SHELL_LOG();
 c = dba.rebootClusterFromCompleteOutage("test_cluster");
 EXPECT_SHELL_LOG_NOT_CONTAINS(reboot_cluster_sql[0]);
@@ -567,7 +597,7 @@ EXPECT_SHELL_LOG_NOT_CONTAINS(reboot_cluster_sql[3]);
 testutil.waitMemberState(__mysql_sandbox_port2, "ONLINE");
 testutil.waitMemberState(__mysql_sandbox_port3, "ONLINE");
 
-//@<> WL#13294: remove instance (dba.logSql = 1).
+//@<> WL#13294: remove instance (logSql = on).
 WIPE_SHELL_LOG();
 c.removeInstance(__sandbox_uri2);
 EXPECT_SHELL_LOG_NOT_CONTAINS(remove_instance_sql[0]);
@@ -575,21 +605,21 @@ EXPECT_SHELL_LOG_CONTAINS(remove_instance_sql[1]);
 EXPECT_SHELL_LOG_CONTAINS(remove_instance_sql[2]);
 EXPECT_SHELL_LOG_CONTAINS(remove_instance_sql[3]);
 
-//@<> WL#13294: drop metadata (dba.logSql = 1).
+//@<> WL#13294: drop metadata (logSql = on).
 session.runSql("SET GLOBAL super_read_only = 'OFF'");
 WIPE_SHELL_LOG();
 dba.dropMetadataSchema({force: true});
 EXPECT_SHELL_LOG_NOT_CONTAINS(drop_metadata_sql[0]);
 EXPECT_SHELL_LOG_CONTAINS(drop_metadata_sql[1]);
 
-//@<> createCluster(adoptFromGR) (dba.logSql = 1).
+//@<> createCluster(adoptFromGR) (logSql = on).
 WIPE_SHELL_LOG();
 c = dba.createCluster('test', {adoptFromGR: true});
 EXPECT_SHELL_LOG_CONTAINS(create_cluster_adopt_sql[0]);
 EXPECT_SHELL_LOG_CONTAINS(create_cluster_adopt_sql[1]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(create_cluster_adopt_sql[2]);
 
-//@<> WL#13294: dissolve (dba.logSql = 1).
+//@<> WL#13294: dissolve (logSql = on).
 WIPE_SHELL_LOG();
 c.dissolve();
 EXPECT_SHELL_LOG_NOT_CONTAINS(dissolve_sql[0]);
@@ -597,8 +627,8 @@ EXPECT_SHELL_LOG_CONTAINS(dissolve_sql[1]);
 EXPECT_SHELL_LOG_CONTAINS(dissolve_sql[2]);
 EXPECT_SHELL_LOG_CONTAINS(dissolve_sql[3]);
 
-//@<> WL#13294: Clean-up (dba.logSql = 1).
-\option dba.logSql = 0
+//@<> WL#13294: Clean-up (logSql = on).
+\option logSql = off
 \option verbose = 0
 session.close();
 testutil.destroySandbox(__mysql_sandbox_port1);
@@ -606,9 +636,9 @@ testutil.destroySandbox(__mysql_sandbox_port2);
 testutil.destroySandbox(__mysql_sandbox_port3);
 
 
-// ---------- WL#13294 (dba.logSql = 2) ----------
+// ---------- WL#13294 (logSql = unfiltered) (replaced logSql = unfiltered) ----------
 
-//@<> WL#13294: Initialization (dba.logSql = 2).
+//@<> WL#13294: Initialization (logSql = unfiltered).
 testutil.deploySandbox(__mysql_sandbox_port1, 'root', {report_host: hostname}, {createRemoteRoot:true});
 testutil.snapshotSandboxConf(__mysql_sandbox_port1);
 testutil.deploySandbox(__mysql_sandbox_port2, "root", {report_host: hostname});
@@ -621,15 +651,15 @@ var mycnf3 = testutil.getSandboxConfPath(__mysql_sandbox_port3);
 
 shell.connect(__sandbox_uri1);
 
-//@<> WL#13294: set dba.logSql = 2.
-\option dba.logSql = 2
+//@<> WL#13294: set logSql = unfiltered.
+\option logSql = unfiltered
 
-//@<> WL#13294: set some configuration incorrectly (that can be fixed without restart) (dba.logSql = 2).
+//@<> WL#13294: set some configuration incorrectly (that can be fixed without restart) (logSql = unfiltered).
 session.runSql("SET sql_log_bin=0");
 session.runSql("SET GLOBAL binlog_format = STATEMENT");
 session.runSql("SET sql_log_bin=1");
 
-//@<> WL#13294: check instance (dba.logSql = 2).
+//@<> WL#13294: check instance (logSql = unfiltered).
 WIPE_SHELL_LOG();
 dba.checkInstanceConfiguration(__sandbox_uri1, {mycnfPath: mycnf1});
 EXPECT_SHELL_LOG_NOT_CONTAINS(check_instance_sql[0]);
@@ -637,25 +667,25 @@ EXPECT_SHELL_LOG_CONTAINS(check_instance_sql[1]);
 EXPECT_SHELL_LOG_CONTAINS(check_instance_sql[2]);
 EXPECT_SHELL_LOG_CONTAINS(check_instance_sql[3]);
 
-//@<> WL#13294: configure instance (dba.logSql = 2).
+//@<> WL#13294: configure instance (logSql = unfiltered).
 WIPE_SHELL_LOG();
 dba.configureInstance(__sandbox_uri1, {mycnfPath: mycnf1});
 EXPECT_SHELL_LOG_CONTAINS(configure_instance_sql[0]);
 EXPECT_SHELL_LOG_CONTAINS(configure_instance_sql[1]);
 EXPECT_SHELL_LOG_CONTAINS(configure_instance_sql[2]);
 
-//@<> WL#13294: create cluster (dba.logSql = 2).
+//@<> WL#13294: create cluster (logSql = unfiltered).
 WIPE_SHELL_LOG();
 var c = dba.createCluster('test', {gtidSetIsComplete: true});
 EXPECT_SHELL_LOG_CONTAINS(create_cluster_sql[0]);
 EXPECT_SHELL_LOG_CONTAINS(create_cluster_sql[1]);
 EXPECT_SHELL_LOG_CONTAINS(create_cluster_sql[2]);
-//@<> WL#13294: create cluster (dba.logSql = 2). < 8.0.23 {VER(<8.0.23)}
+//@<> WL#13294: create cluster (logSql = unfiltered). < 8.0.23 {VER(<8.0.23)}
 EXPECT_SHELL_LOG_CONTAINS(create_cluster_sql[3]);
-//@<> WL#13294: create cluster (dba.logSql = 2). >= 8.0.23 {VER(>=8.0.23)}
+//@<> WL#13294: create cluster (logSql = unfiltered). >= 8.0.23 {VER(>=8.0.23)}
 EXPECT_SHELL_LOG_CONTAINS(create_cluster_sql[4]);
 
-//@<> WL#13294: add instance (dba.logSql = 2).
+//@<> WL#13294: add instance (logSql = unfiltered).
 WIPE_SHELL_LOG();
 c.addInstance(__sandbox_uri2);
 EXPECT_SHELL_LOG_CONTAINS(add_instance_sql[0]);
@@ -665,21 +695,21 @@ EXPECT_SHELL_LOG_CONTAINS(add_instance_sql[3]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(add_instance_sql[4]);
 EXPECT_SHELL_LOG_CONTAINS(add_instance_sql[5]);
 
-//@<> WL#13294: add another instance (dba.logSql = 2).
+//@<> WL#13294: add another instance (logSql = unfiltered).
 c.addInstance(__sandbox_uri3);
 
-//@<> WL#13294: persist GR configuration settings for 5.7 servers (dba.logSql = 2). {VER(<8.0.11)}
+//@<> WL#13294: persist GR configuration settings for 5.7 servers (logSql = unfiltered). {VER(<8.0.11)}
 dba.configureInstance('root:root@localhost:' + __mysql_sandbox_port1, {mycnfPath: mycnf1});
 dba.configureInstance('root:root@localhost:' + __mysql_sandbox_port2, {mycnfPath: mycnf2});
 dba.configureInstance('root:root@localhost:' + __mysql_sandbox_port3, {mycnfPath: mycnf3});
 
-//@<> WL#13294: Stop GR on an instance to use rejoin instance (dba.logSql = 2).
+//@<> WL#13294: Stop GR on an instance to use rejoin instance (logSql = unfiltered).
 shell.connect(__sandbox_uri3);
 session.runSql("STOP GROUP_REPLICATION");
 session.close();
 shell.connect(__sandbox_uri1);
 
-//@<> WL#13294: rejoin instance (dba.logSql = 2).
+//@<> WL#13294: rejoin instance (logSql = unfiltered).
 WIPE_SHELL_LOG();
 c.rejoinInstance(__sandbox_uri3);
 EXPECT_SHELL_LOG_CONTAINS(rejoin_instance_sql[0]);
@@ -687,13 +717,13 @@ EXPECT_SHELL_LOG_CONTAINS(rejoin_instance_sql[1]);
 
 testutil.waitMemberState(__mysql_sandbox_port3, "ONLINE");
 
-//@<> WL#13294: get cluster (dba.logSql = 2).
+//@<> WL#13294: get cluster (logSql = unfiltered).
 WIPE_SHELL_LOG();
 var c = dba.getCluster();
 EXPECT_SHELL_LOG_CONTAINS(get_cluster_sql[0]);
 EXPECT_SHELL_LOG_CONTAINS(get_cluster_sql[1]);
 
-//@<> WL#13294: status (dba.logSql = 2).
+//@<> WL#13294: status (logSql = unfiltered).
 WIPE_SHELL_LOG();
 c.status();
 EXPECT_SHELL_LOG_CONTAINS(status_sql[0]);
@@ -701,93 +731,93 @@ EXPECT_SHELL_LOG_CONTAINS(status_sql[1]);
 EXPECT_SHELL_LOG_CONTAINS(status_sql[2]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(status_sql[3]);
 
-//@<> WL#13294: describe (dba.logSql = 2).
+//@<> WL#13294: describe (logSql = unfiltered).
 WIPE_SHELL_LOG();
 c.describe();
 EXPECT_SHELL_LOG_CONTAINS(describe_sql[0]);
 EXPECT_SHELL_LOG_CONTAINS(describe_sql[1]);
 
-//@<> WL#13294: set option (dba.logSql = 2).
+//@<> WL#13294: set option (logSql = unfiltered).
 WIPE_SHELL_LOG();
 c.setOption("clusterName", "test_cluster");
 EXPECT_SHELL_LOG_CONTAINS(set_option_sql[0]);
 EXPECT_SHELL_LOG_CONTAINS(set_option_sql[1]);
 
-//@<> WL#13294: set instance option (dba.logSql = 2).
+//@<> WL#13294: set instance option (logSql = unfiltered).
 WIPE_SHELL_LOG();
 c.setInstanceOption(__sandbox_uri3, "label", "instance_3_label");
 EXPECT_SHELL_LOG_CONTAINS(set_instance_option_sql[0]);
 EXPECT_SHELL_LOG_CONTAINS(set_instance_option_sql[1]);
 
-//@<> WL#13294: options (dba.logSql = 2).
+//@<> WL#13294: options (logSql = unfiltered).
 WIPE_SHELL_LOG();
 c.options();
 EXPECT_SHELL_LOG_CONTAINS(options_sql[0]);
 EXPECT_SHELL_LOG_CONTAINS(options_sql[1]);
 
-//@<> WL#13294: remove metadata from one instance for rescan() to add it (dba.logSql = 2).
+//@<> WL#13294: remove metadata from one instance for rescan() to add it (logSql = unfiltered).
 session.runSql("DELETE FROM mysql_innodb_cluster_metadata.instances WHERE instance_name=?", ["instance_3_label"]);
 
-//@<> WL#13294: rescan (dba.logSql = 2).
+//@<> WL#13294: rescan (logSql = unfiltered).
 WIPE_SHELL_LOG();
-c.rescan({addInstances: "AUTO"});
+c.rescan({addUnmanaged: "true"});
 EXPECT_SHELL_LOG_CONTAINS(rescan_sql[0]);
 EXPECT_SHELL_LOG_CONTAINS(rescan_sql[1]);
 EXPECT_SHELL_LOG_CONTAINS(rescan_sql[2]);
 
-//@<> WL#13294: set primary instance (dba.logSql = 2). {VER(>=8.0.13)}
+//@<> WL#13294: set primary instance (logSql = unfiltered). {VER(>=8.0.13)}
 WIPE_SHELL_LOG();
 c.setPrimaryInstance(__sandbox_uri2);
 EXPECT_SHELL_LOG_CONTAINS(set_primary_instance_sql[0]);
 EXPECT_SHELL_LOG_CONTAINS(set_primary_instance_sql[1]);
 
-//@<> Bug#33538559: set primary instance with timeout (dba.logSql = 2). {VER(>=8.0.29)}
+//@<> Bug#33538559: set primary instance with timeout (logSql = unfiltered). {VER(>=8.0.29)}
 WIPE_SHELL_LOG();
 c.setPrimaryInstance(__sandbox_uri3, {runningTransactionsTimeout: 11});
 EXPECT_SHELL_LOG_CONTAINS(set_primary_instance_timeout_sql[0]);
 EXPECT_SHELL_LOG_CONTAINS(set_primary_instance_timeout_sql[1]);
 
-//@<> WL#13294: switch to multi-primary (dba.logSql = 2). {VER(>=8.0.13)}
+//@<> WL#13294: switch to multi-primary (logSql = unfiltered). {VER(>=8.0.13)}
 WIPE_SHELL_LOG();
 c.switchToMultiPrimaryMode();
 EXPECT_SHELL_LOG_CONTAINS(switch_multi_primary_sql[0]);
 EXPECT_SHELL_LOG_CONTAINS(switch_multi_primary_sql[1]);
 EXPECT_SHELL_LOG_CONTAINS(switch_multi_primary_sql[2]);
 
-//@<> WL#13294: switch to single-primary (dba.logSql = 2). {VER(>=8.0.13)}
+//@<> WL#13294: switch to single-primary (logSql = unfiltered). {VER(>=8.0.13)}
 WIPE_SHELL_LOG();
 c.switchToSinglePrimaryMode(__sandbox_uri1);
 EXPECT_SHELL_LOG_CONTAINS(switch_single_primary_sql[0]);
 EXPECT_SHELL_LOG_CONTAINS(switch_single_primary_sql[1]);
 EXPECT_SHELL_LOG_CONTAINS(switch_single_primary_sql[2]);
 
-//@<> WL#13294: disable gr_start_on_boot on all instances to kill (dba.logSql = 2).
+//@<> WL#13294: disable gr_start_on_boot on all instances to kill (logSql = unfiltered).
 //disable_auto_rejoin(__mysql_sandbox_port1);
 disable_auto_rejoin(__mysql_sandbox_port2);
 disable_auto_rejoin(__mysql_sandbox_port3);
 shell.connect(__sandbox_uri1);
 
-//@<> WL#13294: lose quorum (dba.logSql = 2).
+//@<> WL#13294: lose quorum (logSql = unfiltered).
 testutil.killSandbox(__mysql_sandbox_port2);
 testutil.killSandbox(__mysql_sandbox_port3);
 testutil.waitMemberState(__mysql_sandbox_port2, "UNREACHABLE,(MISSING)");
 testutil.waitMemberState(__mysql_sandbox_port3, "UNREACHABLE,(MISSING)");
 
-//@<> WL#13294: force quorum (dba.logSql = 2).
+//@<> WL#13294: force quorum (logSql = unfiltered).
 WIPE_SHELL_LOG();
 c.forceQuorumUsingPartitionOf(__sandbox_uri1);
 EXPECT_SHELL_LOG_CONTAINS(force_quorum_sql[0]);
 EXPECT_SHELL_LOG_CONTAINS(force_quorum_sql[1]);
 EXPECT_SHELL_LOG_CONTAINS(force_quorum_sql[2]);
 
-//@<> WL#13294: restart killed instances (dba.logSql = 2).
+//@<> WL#13294: restart killed instances (logSql = unfiltered).
 testutil.startSandbox(__mysql_sandbox_port2);
 testutil.startSandbox(__mysql_sandbox_port3);
 
-//@<> WL#13294: Stop GR on remaining cluster instance (dba.logSql = 2).
+//@<> WL#13294: Stop GR on remaining cluster instance (logSql = unfiltered).
 session.runSql("STOP GROUP_REPLICATION");
 
-//@<> WL#13294: reboot cluster (dba.logSql = 2).
+//@<> WL#13294: reboot cluster (logSql = unfiltered).
 WIPE_SHELL_LOG();
 c = dba.rebootClusterFromCompleteOutage("test_cluster");
 EXPECT_SHELL_LOG_CONTAINS(reboot_cluster_sql[0]);
@@ -798,7 +828,7 @@ EXPECT_SHELL_LOG_CONTAINS(reboot_cluster_sql[3]);
 testutil.waitMemberState(__mysql_sandbox_port2, "ONLINE");
 testutil.waitMemberState(__mysql_sandbox_port3, "ONLINE");
 
-//@<> WL#13294: remove instance (dba.logSql = 2).
+//@<> WL#13294: remove instance (logSql = unfiltered).
 WIPE_SHELL_LOG();
 c.removeInstance(__sandbox_uri2);
 EXPECT_SHELL_LOG_CONTAINS(remove_instance_sql[0]);
@@ -806,21 +836,21 @@ EXPECT_SHELL_LOG_CONTAINS(remove_instance_sql[1]);
 EXPECT_SHELL_LOG_CONTAINS(remove_instance_sql[2]);
 EXPECT_SHELL_LOG_CONTAINS(remove_instance_sql[3]);
 
-//@<> WL#13294: drop metadata (dba.logSql = 2).
+//@<> WL#13294: drop metadata (logSql = unfiltered).
 session.runSql("SET GLOBAL super_read_only = 'OFF'");
 WIPE_SHELL_LOG();
 dba.dropMetadataSchema({force: true});
 EXPECT_SHELL_LOG_CONTAINS(drop_metadata_sql[0]);
 EXPECT_SHELL_LOG_CONTAINS(drop_metadata_sql[1]);
 
-//@<> createCluster(adoptFromGR) (dba.logSql = 2).
+//@<> createCluster(adoptFromGR) (logSql = unfiltered).
 WIPE_SHELL_LOG();
 c = dba.createCluster('test', {adoptFromGR: true});
 EXPECT_SHELL_LOG_CONTAINS(create_cluster_adopt_sql[0]);
 EXPECT_SHELL_LOG_CONTAINS(create_cluster_adopt_sql[1]);
 EXPECT_SHELL_LOG_CONTAINS(create_cluster_adopt_sql[2]);
 
-//@<> WL#13294: dissolve (dba.logSql = 2).
+//@<> WL#13294: dissolve (logSql = unfiltered).
 WIPE_SHELL_LOG();
 c.dissolve();
 EXPECT_SHELL_LOG_CONTAINS(dissolve_sql[0]);
@@ -828,8 +858,8 @@ EXPECT_SHELL_LOG_CONTAINS(dissolve_sql[1]);
 EXPECT_SHELL_LOG_CONTAINS(dissolve_sql[2]);
 EXPECT_SHELL_LOG_CONTAINS(dissolve_sql[3]);
 
-//@<> WL#13294: Clean-up (dba.logSql = 2).
-\option dba.logSql = 0
+//@<> WL#13294: Clean-up (logSql = unfiltered).
+\option logSql = off
 \option verbose = 0
 session.close();
 testutil.destroySandbox(__mysql_sandbox_port1);
diff --git a/unittest/scripts/auto/js_adminapi/scripts/router_management.js b/unittest/scripts/auto/js_adminapi/scripts/router_management.js
index 0eb12be021..908d7f311b 100644
--- a/unittest/scripts/auto/js_adminapi/scripts/router_management.js
+++ b/unittest/scripts/auto/js_adminapi/scripts/router_management.js
@@ -22,33 +22,40 @@ var cr_router3 = "routerhost2::";
 
 session.runSql("INSERT mysql_innodb_cluster_metadata.routers VALUES (4, '', 'mysqlrouter', 'routerhost2', '8.1.0', '2023-04-26 11:22:33', '{\"bootstrapTargetType\": \"cluster\"}', ?, NULL, NULL)", [cluster_id]);
 
-//@<> cluster.routingOptions on invalid routers
-EXPECT_THROWS(function(){ cluster.routingOptions("invalid_router"); }, "Router 'invalid_router' is not registered in the cluster");
+//@<> cluster.routerOptions on invalid routers
+EXPECT_THROWS(function(){ cluster.routerOptions({"router": "invalid_router"}); }, "Router 'invalid_router' is not registered in the cluster");
 
-EXPECT_THROWS(function(){ cluster.routingOptions("routerhost2"); }, "Router 'routerhost2' is not registered in the cluster");
+EXPECT_THROWS(function(){ cluster.routerOptions({"router": "routerhost2"}); }, "Router 'routerhost2' is not registered in the cluster");
 
-EXPECT_THROWS(function(){ cluster.routingOptions("another"); }, "Router 'another' is not registered in the cluster");
+EXPECT_THROWS(function(){ cluster.routerOptions({"router": "another"}); }, "Router 'another' is not registered in the cluster");
 
-EXPECT_THROWS(function(){ cluster.routingOptions("::system"); }, "Router '::system' is not registered in the cluster");
+EXPECT_THROWS(function(){ cluster.routerOptions({"router": "::system"}); }, "Router '::system' is not registered in the cluster");
 
-//@<> cluster.routingOptions() with all defaults
-cluster.routingOptions();
+//@<> cluster.routerOptions() with all defaults
+cluster.routerOptions();
 
 EXPECT_OUTPUT_CONTAINS_MULTILINE(`
 {
-    "clusterName": "cluster",
-    "global": {
-        "guideline": null,
-        "read_only_targets": "secondaries",
-        "stats_updates_frequency": null,
-        "tags": {},
-        "unreachable_quorum_allowed_traffic": null
-    },
+    "clusterName": "cluster", 
+    "configuration": {
+        "routing_rules": {
+            "read_only_targets": "secondaries", 
+            "tags": {}
+        }
+    }, 
     "routers": {
-        "routerhost1::system": {},
-        "routerhost2::": {},
-        "routerhost2::another": {},
-        "routerhost2::system": {}
+        "routerhost1::system": {
+            "configuration": {}
+        }, 
+        "routerhost2::": {
+            "configuration": {}
+        }, 
+        "routerhost2::another": {
+            "configuration": {}
+        }, 
+        "routerhost2::system": {
+            "configuration": {}
+        }
     }
 }
 `
@@ -56,43 +63,49 @@ EXPECT_OUTPUT_CONTAINS_MULTILINE(`
 
 //@<> cluster.setRoutingOption, all valid values, tag
 cluster.setRoutingOption(router1, "tag:test_tag", 567);
-EXPECT_JSON_EQ(567, cluster.routingOptions(router1)[router1]["tags"]["test_tag"]);
-EXPECT_JSON_EQ(567, cluster.routingOptions()["routers"][router1]["tags"]["test_tag"]);
+EXPECT_JSON_EQ(567, cluster.routerOptions({"router": router1, "extended": 2})["routers"][router1]["configuration"]["routing_rules"]["tags"]["test_tag"]);
+EXPECT_JSON_EQ(567, cluster.routerOptions({"extended": 2})["routers"][router1]["configuration"]["routing_rules"]["tags"]["test_tag"]);
 
 cluster.setRoutingOption("tag:test_tag", 567);
-EXPECT_JSON_EQ(567, cluster.routingOptions()["global"]["tags"]["test_tag"]);
+EXPECT_JSON_EQ(567, cluster.routerOptions()["configuration"]["routing_rules"]["tags"]["test_tag"]);
 
 cluster.setRoutingOption("tag:test_tag", null);
 cluster.setRoutingOption(router1, "tag:test_tag", null);
-EXPECT_JSON_EQ(null, cluster.routingOptions()["routers"][router1]["tags"]["test_tag"]);
-EXPECT_JSON_EQ(null, cluster.routingOptions()["global"]["tags"]["test_tag"]);
+EXPECT_JSON_EQ({}, cluster.routerOptions({"extended": 2})["routers"][router1]["configuration"]["routing_rules"]["tags"]);
+EXPECT_JSON_EQ({}, cluster.routerOptions()["configuration"]["routing_rules"]["tags"]);
+
+cluster.setRoutingOption(router1, "tag:test_tag", 567);
+cluster.setRoutingOption("tag:test_tag", 567);
 
 cluster.setRoutingOption("tags", null);
 cluster.setRoutingOption(router1, "tags", null);
-EXPECT_JSON_EQ(undefined, cluster.routingOptions()["routers"][router1]["tags"]);
-EXPECT_JSON_EQ({}, cluster.routingOptions()["global"]["tags"]);
+EXPECT_JSON_EQ({}, cluster.routerOptions({"extended": 2})["routers"][router1]["configuration"]["routing_rules"]["tags"]);
+EXPECT_JSON_EQ({}, cluster.routerOptions()["configuration"]["routing_rules"]["tags"]);
 
 //@<> cluster.setRoutingOption, all valid values
 function CHECK_SET_ROUTING_OPTION(option, value, expected_value) {
-  orig_options = cluster.routingOptions();
+  orig_options = cluster.routerOptions({"extended": 2});
 
-  router_options = cluster.routingOptions(router1);
-  global_options = cluster.routingOptions();
+  router_options = cluster.routerOptions({"router": router1, "extended": 2});
+  global_options = cluster.routerOptions({"extended": 2});
 
   cluster.setRoutingOption(router1, option, value);
-  router_options[router1][option] = expected_value;
-  global_options["routers"][router1][option] = expected_value;
-  EXPECT_JSON_EQ(router_options, cluster.routingOptions(router1), "router check");
-  EXPECT_JSON_EQ(global_options, cluster.routingOptions(), "router check 2");
+  router_options["routers"][router1]["configuration"]["routing_rules"][option] = expected_value;
+  global_options["routers"][router1]["configuration"]["routing_rules"][option] = expected_value;
+  EXPECT_JSON_EQ(router_options, cluster.routerOptions({"router": router1, "extended": 2}), "router check");
+  EXPECT_JSON_EQ(global_options, cluster.routerOptions({"extended": 2}), "router check 2");
 
   cluster.setRoutingOption(option, value);
-  global_options["global"][option] = expected_value;
-  EXPECT_JSON_EQ(global_options, cluster.routingOptions(), "global check");
+  global_options["configuration"]["routing_rules"][option] = expected_value;
+  global_options["routers"][router2]["configuration"]["routing_rules"][option] = expected_value;
+  global_options["routers"][router3]["configuration"]["routing_rules"][option] = expected_value;
+  global_options["routers"][cr_router3]["configuration"]["routing_rules"][option] = expected_value;
+  EXPECT_JSON_EQ(global_options, cluster.routerOptions({"extended": 2}), "global check");
 
   // setting option to null should reset to default
   cluster.setRoutingOption(option, null);
   cluster.setRoutingOption(router1, option, null);
-  EXPECT_JSON_EQ(orig_options, cluster.routingOptions(), "original check");
+  EXPECT_JSON_EQ(orig_options, cluster.routerOptions({"extended": 2}), "original check");
 }
 
 CHECK_SET_ROUTING_OPTION("read_only_targets", "all", "all");
@@ -102,42 +115,52 @@ CHECK_SET_ROUTING_OPTION("read_only_targets", "secondaries", "secondaries");
 CHECK_SET_ROUTING_OPTION('tags', {}, {});
 CHECK_SET_ROUTING_OPTION('tags', { "a": 123 }, { "a": 123 });
 
-//@<> default values filled in when metadata is missing some option (e.g. upgrade)
-var full_options = cluster.routingOptions();
+//@<> default values are not filled in when metadata is missing some option (e.g. upgrade)
+var full_options = cluster.routerOptions({"extended": 2});
+
+EXPECT_EQ(full_options["configuration"]["routing_rules"].hasOwnProperty("read_only_targets"),true)
+EXPECT_EQ(full_options["configuration"]["routing_rules"].hasOwnProperty("tags"),true)
 
 var router_options = session.runSql("select router_options from mysql_innodb_cluster_metadata.clusters").fetchOne()[0]
 session.runSql("update mysql_innodb_cluster_metadata.clusters set router_options='{}'");
 
-EXPECT_JSON_EQ(full_options, cluster.routingOptions());
+EXPECT_EQ(cluster.routerOptions({"extended": 2})["configuration"]["routing_rules"].hasOwnProperty("read_only_targets"),false)
+EXPECT_EQ(cluster.routerOptions({"extended": 2})["configuration"]["routing_rules"].hasOwnProperty("tags"),false)
+
+EXPECT_JSON_EQ({}, cluster.routerOptions({"extended": 2})["configuration"]["routing_rules"])
+EXPECT_JSON_EQ({}, cluster.routerOptions({"extended": 2})["routers"][router1]["configuration"]["routing_rules"])
+EXPECT_JSON_EQ({}, cluster.routerOptions({"extended": 2})["routers"][router2]["configuration"]["routing_rules"])
+EXPECT_JSON_EQ({}, cluster.routerOptions({"extended": 2})["routers"][router3]["configuration"]["routing_rules"])
+EXPECT_JSON_EQ({}, cluster.routerOptions({"extended": 2})["routers"][cr_router3]["configuration"]["routing_rules"])
 
 session.runSql("update mysql_innodb_cluster_metadata.clusters set router_options=?", [router_options]);
-EXPECT_JSON_EQ(full_options, cluster.routingOptions());
+EXPECT_JSON_EQ(full_options, cluster.routerOptions({"extended": 2}));
 
 //@<> reset option
 cluster.setRoutingOption("read_only_targets", "read_replicas");
 cluster.setRoutingOption(router1, "read_only_targets", "all");
-var orig = cluster.routingOptions();
+var orig = cluster.routerOptions();
 
 cluster.setRoutingOption(router1, "read_only_targets", null);
 delete orig["routers"][router1]["read_only_targets"];
-EXPECT_JSON_EQ(orig, cluster.routingOptions());
+EXPECT_JSON_EQ(orig, cluster.routerOptions());
 
 //@<> set individual tags
 cluster.setRoutingOption("tags", {"old":"oldvalue"});
 cluster.setRoutingOption("tag:test_tag", 1234);
 cluster.setRoutingOption("tag:bla", "test");
-EXPECT_JSON_EQ({"old":"oldvalue", "test_tag":1234, "bla": "test"}, cluster.routingOptions()["global"]["tags"]);
+EXPECT_JSON_EQ({"old":"oldvalue", "test_tag":1234, "bla": "test"}, cluster.routerOptions()["configuration"]["routing_rules"]["tags"]);
 
 cluster.setRoutingOption("tags", {});
-EXPECT_JSON_EQ({}, cluster.routingOptions()["global"]["tags"]);
+EXPECT_JSON_EQ({}, cluster.routerOptions()["configuration"]["routing_rules"]["tags"]);
 
 cluster.setRoutingOption(router1, "tags", {"old":"oldvalue"});
 cluster.setRoutingOption(router1, "tag:test_tag", 1234);
 cluster.setRoutingOption(router1, "tag:bla", "test");
-EXPECT_JSON_EQ({"old":"oldvalue", "test_tag":1234, "bla": "test"}, cluster.routingOptions()["routers"][router1]["tags"]);
+EXPECT_JSON_EQ({"old":"oldvalue", "test_tag":1234, "bla": "test"}, cluster.routerOptions({"extended": 2})["routers"][router1]["configuration"]["routing_rules"]["tags"]);
 
 cluster.setRoutingOption(router1, "tags", {});
-EXPECT_JSON_EQ({}, cluster.routingOptions()["routers"][router1]["tags"]);
+EXPECT_JSON_EQ({}, cluster.routerOptions({"extended": 2})["routers"][router1]["configuration"]["routing_rules"]["tags"]);
 
 //@<> cluster.setRoutingOption for a router, invalid values
 EXPECT_THROWS(function(){ cluster.setRoutingOption(router1, "read_only_targets", 'any_not_supported_value'); },
@@ -158,8 +181,8 @@ EXPECT_THROWS(function(){ cluster.setRoutingOption("another", 'read_only_targets
 EXPECT_THROWS(function(){ cluster.setRoutingOption("::system", 'read_only_targets', 'all'); }, "Router '::system' is not part of this topology");
 
 //@<> check types of cluster router option values
-options = cluster.routingOptions();
-EXPECT_EQ("string", typeof options["global"]["read_only_targets"]);
+options = cluster.routerOptions();
+EXPECT_EQ("string", typeof options["configuration"]["routing_rules"]["read_only_targets"]);
 
 options = JSON.parse(session.runSql("select router_options from mysql_innodb_cluster_metadata.clusters").fetchOne()[0]);
 EXPECT_EQ("string", typeof options["read_only_targets"]);
diff --git a/unittest/scripts/auto/js_adminapi/scripts/routing_guideline.js b/unittest/scripts/auto/js_adminapi/scripts/routing_guideline.js
index da165790d6..8238c728c9 100644
--- a/unittest/scripts/auto/js_adminapi/scripts/routing_guideline.js
+++ b/unittest/scripts/auto/js_adminapi/scripts/routing_guideline.js
@@ -42,11 +42,8 @@ var rgid_router_options = session.runSql("select json_unquote(router_options->'$
 
 EXPECT_EQ(null, rgid_router_options);
 
-var routing_options = cluster.routingOptions();
-EXPECT_EQ(null, routing_options["global"]["guideline"]);
-
 var router_options = cluster.routerOptions();
-EXPECT_EQ(undefined, router_options["configuration"]["routing_rules"]["guideline"]);
+EXPECT_EQ(false, router_options["configuration"]["routing_rules"].hasOwnProperty("guideline"));
 
 // Manually set the guideline as the default
 session.runSql("UPDATE mysql_innodb_cluster_metadata.clusters SET router_options = JSON_SET(router_options, '$.guideline', ?) WHERE cluster_id = ?", [rgid, cid]);
@@ -58,8 +55,8 @@ EXPECT_OUTPUT_CONTAINS(`NOTE: Routing Guideline 'default_cluster_rg' is the guid
 EXPECT_OUTPUT_CONTAINS(`Routing Guideline 'anotherguideline' successfully created.`);
 
 // Verify the new one is not the active one
-var routing_options = cluster.routingOptions();
-EXPECT_EQ("default_cluster_rg", routing_options["global"]["guideline"]);
+var router_options = cluster.routerOptions();
+EXPECT_EQ("default_cluster_rg", router_options["configuration"]["routing_rules"]["guideline"]);
 
 // Run common tests for <RoutingGuideline>
 const expected_default_cluster_guideline_show_output = `
@@ -1848,6 +1845,19 @@ Unreferenced servers
 EXPECT_NO_THROWS(function() { rg2.show(); });
 EXPECT_OUTPUT_CONTAINS_MULTILINE(expected_show_output);
 
+//@<> .show() must require the 'router' option when a destination includes $.router.* vars.
+
+// Add a new destination with a $.router var
+rg.addDestination("RouterVar", "$.server.memberRole = SECONDARY AND $.server.clusterName = $.router.localCluster")
+
+// Check the error
+EXPECT_THROWS(function(){ rg.show(); }, "Option 'router' not set: The Routing Guideline contains destinations configured to use router-specific variables.");
+EXPECT_OUTPUT_CONTAINS("ERROR: The Routing Guideline contains destinations configured to use router-specific variables (e.g., '$.router.localCluster'), but the 'router' option is not set. This may cause potential mismatches with the live router behavior. Please set the 'router' option to specify the Router that should be used for evaluation.");
+
+// Confirm no error when the option is given
+EXPECT_NO_THROWS(function() { rg.show({router: router1}); });
+
+rg.removeDestination("RouterVar");
 
 //@<> import_routing_guideline() - Duplicate guideline
 EXPECT_THROWS(function(){ cluster.importRoutingGuideline(rg2_path);}, "A Routing Guideline with the name 'rg2' already exists");
@@ -1969,11 +1979,38 @@ EXPECT_NO_THROWS(function(){ cluster.removeRoutingGuideline("rg2"); });
 EXPECT_NO_THROWS(function(){ test_import = cluster.importRoutingGuideline(test_import_valid);});
 
 // Confirm that the guideline was NOT set as the active one
-var routing_options = cluster.routingOptions();
-EXPECT_EQ(null, routing_options["global"]["guideline"]);
+var router_options = cluster.routerOptions();
+EXPECT_EQ(false, router_options["configuration"]["routing_rules"].hasOwnProperty("guideline"));
+
+//@<> import_routing_guideline() - 'rename' and 'force' cannot be use simultaneously
+EXPECT_THROWS(function(){ cluster.importRoutingGuideline(test_import_valid, {force: true, rename: "foo"});}, "Options 'force' and 'rename' are mutually exclusive");
+
+//@<> import_routing_guideline() - 'rename' must allow renaming a guideline that has a name which is duplicate of an existing guideline
+
+// Rename with the same name must fail
+EXPECT_THROWS(function(){ cluster.importRoutingGuideline(test_import_valid, { rename: "test_import"});}, "A Routing Guideline with the name 'test_import' already exists");
 
+// Rename with an empty value must fail
+EXPECT_THROWS(function(){ cluster.importRoutingGuideline(test_import_valid, { rename: ""});}, "Option 'rename' cannot be empty.");
+
+// Rename with a valid non-duplicate name must succeed
+EXPECT_NO_THROWS(function(){ test_import = cluster.importRoutingGuideline(test_import_valid, { rename: "test_import_new_name"});});
+
+// Confirm that the guideline was NOT set as the active one
 var router_options = cluster.routerOptions();
-EXPECT_EQ(undefined, router_options["configuration"]["routing_rules"]["guideline"]);
+EXPECT_EQ(false, router_options["configuration"]["routing_rules"].hasOwnProperty("guideline"));
+
+// Confirm the guideline was renamed
+EXPECT_EQ("test_import_new_name", test_import.name);
+
+// Confirm the guideline was imported into the topology
+var rgs;
+EXPECT_NO_THROWS(function() { rgs = cluster.routingGuidelines(); } );
+
+var row = rgs.fetchOne();
+EXPECT_EQ("test_import", row[0]);
+row = rgs.fetchOne();
+EXPECT_EQ("test_import_new_name", row[0]);
 
 // Backward compatibility tests
 
diff --git a/unittest/scripts/auto/js_adminapi/scripts/simple_from_secondary.js b/unittest/scripts/auto/js_adminapi/scripts/simple_from_secondary.js
index de370ee4da..7545b82f8e 100644
--- a/unittest/scripts/auto/js_adminapi/scripts/simple_from_secondary.js
+++ b/unittest/scripts/auto/js_adminapi/scripts/simple_from_secondary.js
@@ -117,7 +117,7 @@ session1.runSql("DELETE FROM mysql_innodb_cluster_metadata.instances WHERE insta
 shell.connect(__sandbox_uri2);
 cluster = dba.getCluster();
 
-cluster.rescan({addInstances:"auto"});
+cluster.rescan({addUnmanaged:"true"});
 
 //@<> listRouters
 cluster_id = session1.runSql("SELECT cluster_id FROM mysql_innodb_cluster_metadata.clusters").fetchOne()[0];
@@ -230,7 +230,7 @@ EXPECT_THROWS(function(){cluster.switchToSinglePrimaryMode()}, "This operation r
 // delete sb3 from the metadata so that rescan picks it up
 session1.runSql("DELETE FROM mysql_innodb_cluster_metadata.instances WHERE instance_name LIKE ?", ["%:"+__mysql_sandbox_port3]);
 
-EXPECT_THROWS(function(){cluster.rescan({addInstances:"auto"})}, "Access denied for user 'root'@'localhost'. Account is locked.");
+EXPECT_THROWS(function(){cluster.rescan({addUnmanaged:"true"})}, "Access denied for user 'root'@'localhost'. Account is locked.");
 
 //@<> listRouters (no primary, should pass)
 cluster.listRouters();
diff --git a/unittest/scripts/auto/js_adminapi/scripts/simple_openid_connect_norecord.js b/unittest/scripts/auto/js_adminapi/scripts/simple_openid_connect_norecord.js
index 4090bc76ac..1e64438d6a 100644
--- a/unittest/scripts/auto/js_adminapi/scripts/simple_openid_connect_norecord.js
+++ b/unittest/scripts/auto/js_adminapi/scripts/simple_openid_connect_norecord.js
@@ -67,7 +67,7 @@ session3 = mysql.getSession(__sandbox_uri3);
 EXPECT_NO_THROWS(function() {
     const sessions = [session1, session2, session3];
     sessions.forEach(session => {
-        session.runSql(`SET PERSIST authentication_openid_connect_configuration='file://${cnfg_path}'`);
+        session.runSql('SET PERSIST authentication_openid_connect_configuration=?', [`file://${cnfg_path}`]);
         session.runSql("SET sql_log_bin = 0");
         session.runSql(`CREATE USER '${OPENID_CONNECT_USER}' IDENTIFIED WITH authentication_openid_connect BY '{"identity_provider":"${OPENID_CONNECT_PROVIDER}","user":"${OPENID_CONNECT_SUBJECT}"}';`);
         session.runSql(`GRANT ALL PRIVILEGES ON *.* TO '${OPENID_CONNECT_USER}' WITH GRANT OPTION;`);
diff --git a/unittest/scripts/auto/js_adminapi/scripts/simple_plain.js b/unittest/scripts/auto/js_adminapi/scripts/simple_plain.js
index dcf60f6bce..ea86ad06d2 100644
--- a/unittest/scripts/auto/js_adminapi/scripts/simple_plain.js
+++ b/unittest/scripts/auto/js_adminapi/scripts/simple_plain.js
@@ -5,7 +5,7 @@
 // and not mutually exclusive. Other tests derived from this should have this removed.
 
 k_mycnf_options = {
-    sql_mode: 'NO_BACKSLASH_ESCAPES,ANSI_QUOTES,NO_AUTO_VALUE_ON_ZERO', 
+    sql_mode: 'NO_BACKSLASH_ESCAPES,ANSI_QUOTES,NO_AUTO_VALUE_ON_ZERO',
     autocommit:1,
     require_secure_transport:1,
     loose_group_replication_consistency:"BEFORE",
@@ -51,8 +51,8 @@ function check_open_sessions(session, ignore_pids) {
     }
 }
 
-function get_uri(port, pwd) {
-    if (pwd === undefined) {
+function root_uri(port, nopwd) {
+    if (nopwd) {
         return `mysql://root@localhost:${port}`
     }
     else {
@@ -60,8 +60,17 @@ function get_uri(port, pwd) {
     }
 }
 
-function connect_no_timeout(uri, pwd) {
-    shell.connect(uri, pwd);
+function admin_uri(port, nopwd) {
+    if (nopwd) {
+        return `mysql://admin@localhost:${port}`
+    }
+    else {
+        return `mysql://admin:${pwdAdmin}@localhost:${port}`
+    }
+}
+
+function connect_no_timeout(uri) {
+    shell.connect(uri);
     session.runSql("SET SESSION wait_timeout = 28800");
 }
 
@@ -90,11 +99,11 @@ expected_pids1 = get_open_sessions(session1);
 expected_pids2 = get_open_sessions(session2);
 
 //@<> configureInstance (__mysql_sandbox_port1)
-EXPECT_DBA_THROWS_PROTOCOL_ERROR(dba.configureInstance, get_uri(__mysql_sandbox_port1, pwd), {mycnfPath: testutil.getSandboxConfPath(__mysql_sandbox_port1)});
+EXPECT_DBA_THROWS_PROTOCOL_ERROR(dba.configureInstance, root_uri(__mysql_sandbox_port1), {mycnfPath: testutil.getSandboxConfPath(__mysql_sandbox_port1)});
 
 WIPE_OUTPUT();
 
-EXPECT_NO_THROWS(function(){ dba.configureInstance(get_uri(__mysql_sandbox_port1, pwd), {mycnfPath: testutil.getSandboxConfPath(__mysql_sandbox_port1)}); });
+EXPECT_NO_THROWS(function(){ dba.configureInstance(root_uri(__mysql_sandbox_port1), {mycnfPath: testutil.getSandboxConfPath(__mysql_sandbox_port1), clusterAdmin:"admin", clusterAdminPassword:pwdAdmin}); });
 
 if (testutil.versionCheck(__version, "<", "8.0.0")) {
     EXPECT_OUTPUT_CONTAINS(`The instance '127.0.0.1:${__mysql_sandbox_port1}' was configured to be used in an InnoDB Cluster.`);
@@ -109,11 +118,11 @@ session1 = mysql.getSession(__sandbox_uri1, pwd);
 expected_pids1 = get_open_sessions(session1);
 
 //@<> configureInstance (__mysql_sandbox_port2)
-EXPECT_DBA_THROWS_PROTOCOL_ERROR(dba.configureInstance, get_uri(__mysql_sandbox_port2, pwd), {clusterAdmin:"admin", clusterAdminPassword:pwdAdmin});
+EXPECT_DBA_THROWS_PROTOCOL_ERROR(dba.configureInstance, root_uri(__mysql_sandbox_port2), {clusterAdmin:"admin", clusterAdminPassword:pwdAdmin});
 
 WIPE_OUTPUT();
 
-EXPECT_NO_THROWS(function(){ dba.configureInstance(get_uri(__mysql_sandbox_port2, pwd), {clusterAdmin:"admin", clusterAdminPassword:pwdAdmin}); });
+EXPECT_NO_THROWS(function(){ dba.configureInstance(root_uri(__mysql_sandbox_port2), {clusterAdmin:"admin", clusterAdminPassword:pwdAdmin}); });
 
 if (testutil.versionCheck(__version, ">=", "8.0.0")) {
     EXPECT_OUTPUT_CONTAINS(`The instance '127.0.0.1:${__mysql_sandbox_port2}' is valid for InnoDB Cluster usage.`);
@@ -131,7 +140,7 @@ EXPECT_THROWS(function() { dba.getCluster();}, `An open session is required to p
 shell.options.useWizards = false;
 
 //@<> createCluster
-connect_no_timeout(__sandbox_uri1, pwd);
+connect_no_timeout(admin_uri(__mysql_sandbox_port1));
 
 expected_pids1 = get_open_sessions(session1);
 
@@ -163,10 +172,10 @@ check_open_sessions(session2, expected_pids2);
 EXPECT_NO_THROWS(function(){ cluster = dba.getCluster(); });
 
 //@<> addInstance unsupported X-protocol error
-EXPECT_CLUSTER_THROWS_PROTOCOL_ERROR(cluster.addInstance, get_uri(__mysql_sandbox_port2), {recoveryMethod:'incremental'});
+EXPECT_CLUSTER_THROWS_PROTOCOL_ERROR(cluster.addInstance, admin_uri(__mysql_sandbox_port2), {recoveryMethod:'incremental'});
 
 //@<> addInstance using clone recovery {VER(>=8.0.17)}
-EXPECT_NO_THROWS(function(){ cluster.addInstance(get_uri(__mysql_sandbox_port2), {recoveryMethod:'clone'}); });
+EXPECT_NO_THROWS(function(){ cluster.addInstance(admin_uri(__mysql_sandbox_port2), {recoveryMethod:'clone'}); });
 
 EXPECT_OUTPUT_CONTAINS(`Clone based recovery selected through the recoveryMethod option`);
 EXPECT_OUTPUT_CONTAINS(`The instance '127.0.0.1:${__mysql_sandbox_port2}' was successfully added to the cluster.`);
@@ -178,7 +187,7 @@ check_open_sessions(session1, expected_pids1);
 check_open_sessions(session2, expected_pids2);
 
 //@<> addInstance using incremental recovery {VER(<8.0.17)}
-EXPECT_NO_THROWS(function(){ cluster.addInstance(get_uri(__mysql_sandbox_port2), {recoveryMethod:'incremental'}); });
+EXPECT_NO_THROWS(function(){ cluster.addInstance(admin_uri(__mysql_sandbox_port2), {recoveryMethod:'incremental'}); });
 
 EXPECT_OUTPUT_CONTAINS(`The instance '127.0.0.1:${__mysql_sandbox_port2}' was successfully added to the cluster.`);
 
@@ -186,31 +195,31 @@ check_open_sessions(session1, expected_pids1);
 check_open_sessions(session2, expected_pids2);
 
 //@<> removeInstance
-EXPECT_CLUSTER_THROWS_PROTOCOL_ERROR(cluster.removeInstance, get_uri(__mysql_sandbox_port2));
+EXPECT_CLUSTER_THROWS_PROTOCOL_ERROR(cluster.removeInstance, admin_uri(__mysql_sandbox_port2));
 
-EXPECT_NO_THROWS(function(){ cluster.removeInstance(get_uri(__mysql_sandbox_port2)); });
+EXPECT_NO_THROWS(function(){ cluster.removeInstance(admin_uri(__mysql_sandbox_port2)); });
 
 check_open_sessions(session1, expected_pids1);
 check_open_sessions(session2, expected_pids2);
 
-EXPECT_NO_THROWS(function(){ cluster.addInstance(get_uri(__mysql_sandbox_port2), {recoveryMethod:'incremental'}); });
+EXPECT_NO_THROWS(function(){ cluster.addInstance(admin_uri(__mysql_sandbox_port2), {recoveryMethod:'incremental'}); });
 
 //@<> setPrimaryInstance
-CHECK_MYSQLX_EXPECT_THROWS_ERROR(`The instance '${get_mysqlx_endpoint(get_uri(__mysql_sandbox_port2))}' does not belong to the Cluster: 'mycluster'.`, cluster.setPrimaryInstance, get_uri(__mysql_sandbox_port2));
+CHECK_MYSQLX_EXPECT_THROWS_ERROR(`The instance '${get_mysqlx_endpoint(admin_uri(__mysql_sandbox_port2))}' does not belong to the Cluster: 'mycluster'.`, cluster.setPrimaryInstance, admin_uri(__mysql_sandbox_port2));
 
-EXPECT_NO_THROWS(function(){ cluster.setPrimaryInstance(get_uri(__mysql_sandbox_port2)); });
+EXPECT_NO_THROWS(function(){ cluster.setPrimaryInstance(admin_uri(__mysql_sandbox_port2)); });
 
 check_open_sessions(session1, expected_pids1);
 check_open_sessions(session2, expected_pids2);
 
-EXPECT_NO_THROWS(function(){ cluster.setPrimaryInstance(get_uri(__mysql_sandbox_port1)); });
+EXPECT_NO_THROWS(function(){ cluster.setPrimaryInstance(admin_uri(__mysql_sandbox_port1)); });
 
 //@<> rejoinInstance
 session2.runSql("STOP group_replication");
 
-EXPECT_CLUSTER_THROWS_PROTOCOL_ERROR(cluster.rejoinInstance, get_uri(__mysql_sandbox_port2));
+EXPECT_CLUSTER_THROWS_PROTOCOL_ERROR(cluster.rejoinInstance, admin_uri(__mysql_sandbox_port2));
 
-EXPECT_NO_THROWS(function(){ cluster.rejoinInstance(get_uri(__mysql_sandbox_port2)); });
+EXPECT_NO_THROWS(function(){ cluster.rejoinInstance(admin_uri(__mysql_sandbox_port2)); });
 
 //@<> skip_replica_start is kept unchanged if the instance does not belong to a ClusterSet {VER(>=8.0.27)}
 var skip_replica_start = session2.runSql("SELECT @@skip_replica_start").fetchOne()[0];
@@ -223,9 +232,9 @@ check_open_sessions(session2, expected_pids2);
 testutil.killSandbox(__mysql_sandbox_port2);
 testutil.waitMemberState(__mysql_sandbox_port2, "(MISSING),UNREACHABLE");
 
-EXPECT_CLUSTER_THROWS_PROTOCOL_ERROR(cluster.forceQuorumUsingPartitionOf, get_uri(__mysql_sandbox_port1, pwd));
+EXPECT_CLUSTER_THROWS_PROTOCOL_ERROR(cluster.forceQuorumUsingPartitionOf, admin_uri(__mysql_sandbox_port1, true));
 
-EXPECT_NO_THROWS(function(){ cluster.forceQuorumUsingPartitionOf(get_uri(__mysql_sandbox_port1, pwd)); });
+EXPECT_NO_THROWS(function(){ cluster.forceQuorumUsingPartitionOf(admin_uri(__mysql_sandbox_port1, true)); });
 
 check_open_sessions(session1, expected_pids1);
 
@@ -234,7 +243,7 @@ session2 = mysql.getSession(__sandbox_uri2, pwd);
 
 expected_pids2 = get_open_sessions(session2);
 
-EXPECT_NO_THROWS(function(){ cluster.rejoinInstance(get_uri(__mysql_sandbox_port2)); });
+EXPECT_NO_THROWS(function(){ cluster.rejoinInstance(admin_uri(__mysql_sandbox_port2)); });
 
 //@<> rebootClusterFromCompleteOutage
 testutil.stopGroup([__mysql_sandbox_port1,__mysql_sandbox_port2], pwd);
@@ -245,7 +254,7 @@ check_open_sessions(session1, expected_pids1);
 check_open_sessions(session2, expected_pids2);
 
 // TODO(alfredo) - the reboot should auto-rejoin all members
-EXPECT_NO_THROWS(function(){ cluster.rejoinInstance(get_uri(__mysql_sandbox_port2)); });
+EXPECT_NO_THROWS(function(){ cluster.rejoinInstance(admin_uri(__mysql_sandbox_port2)); });
 testutil.waitMemberState(__mysql_sandbox_port2, "ONLINE");
 
 //@<> setOption
@@ -259,9 +268,9 @@ custom_weigth=50;
 //@<> setInstanceOption
 custom_weigth=20;
 
-EXPECT_CLUSTER_THROWS_PROTOCOL_ERROR(cluster.setInstanceOption, get_uri(__mysql_sandbox_port1), "memberWeight", custom_weigth);
+EXPECT_CLUSTER_THROWS_PROTOCOL_ERROR(cluster.setInstanceOption, admin_uri(__mysql_sandbox_port1), "memberWeight", custom_weigth);
 
-EXPECT_NO_THROWS(function(){ cluster.setInstanceOption(get_uri(__mysql_sandbox_port1), "memberWeight", custom_weigth); });
+EXPECT_NO_THROWS(function(){ cluster.setInstanceOption(admin_uri(__mysql_sandbox_port1), "memberWeight", custom_weigth); });
 
 check_open_sessions(session1, expected_pids1);
 check_open_sessions(session2, expected_pids2);
@@ -284,9 +293,9 @@ EXPECT_NO_THROWS(function(){ cluster.switchToSinglePrimaryMode(); });
 check_open_sessions(session1, expected_pids1);
 check_open_sessions(session2, expected_pids2);
 
-EXPECT_NO_THROWS(function(){ cluster.setPrimaryInstance(get_uri(__mysql_sandbox_port1)); });
+EXPECT_NO_THROWS(function(){ cluster.setPrimaryInstance(admin_uri(__mysql_sandbox_port1)); });
 
-connect_no_timeout(__sandbox_uri1, pwd);
+connect_no_timeout(root_uri(__mysql_sandbox_port1));
 cluster = dba.getCluster(); // shouldn't be necessary
 
 //@<> rescan
diff --git a/unittest/scripts/auto/js_adminapi/scripts/ssl_auth.js b/unittest/scripts/auto/js_adminapi/scripts/ssl_auth.js
index a2fa3e7a59..a59d4d4651 100644
--- a/unittest/scripts/auto/js_adminapi/scripts/ssl_auth.js
+++ b/unittest/scripts/auto/js_adminapi/scripts/ssl_auth.js
@@ -536,16 +536,9 @@ if (__version_num >= 80023) {
     EXPECT_NO_THROWS(function() { cluster.addReplicaInstance(__sandbox_uri3, { certSubject: `/CN=${hostname}` }); });
 }
 
-//@<> FR17 test if rescan with addInstances errors out
+//@<> FR17 test if rescan with addUnmanaged errors out
 EXPECT_THROWS(function() {
-    cluster.rescan({addInstances: [`${hostname}:${__mysql_sandbox_port2}`]});
-}, "Can't automatically add unrecognized members to the cluster when memberAuthType is 'CERT_SUBJECT_PASSWORD'.");
-EXPECT_OUTPUT_CONTAINS("Unrecognized members were detected in the group, but the cluster is configured to require SSL certificate authentication. Please stop GR on those members and then add them to the cluster using cluster.addInstance() with the appropriate authentication options.");
-
-WIPE_OUTPUT();
-
-EXPECT_THROWS(function() {
-    cluster.rescan({addInstances: "auto"});
+    cluster.rescan({addUnmanaged: true});
 }, "Can't automatically add unrecognized members to the cluster when memberAuthType is 'CERT_SUBJECT_PASSWORD'.");
 EXPECT_OUTPUT_CONTAINS("Unrecognized members were detected in the group, but the cluster is configured to require SSL certificate authentication. Please stop GR on those members and then add them to the cluster using cluster.addInstance() with the appropriate authentication options.");
 
diff --git a/unittest/scripts/auto/js_adminapi/validation/cluster_rescan.js b/unittest/scripts/auto/js_adminapi/validation/cluster_rescan.js
index 2c5d2c9753..883935c19d 100644
--- a/unittest/scripts/auto/js_adminapi/validation/cluster_rescan.js
+++ b/unittest/scripts/auto/js_adminapi/validation/cluster_rescan.js
@@ -11,215 +11,7 @@ Result of the rescanning operation for the 'c' cluster:
     "updatedInstances": []
 }
 
-//@<OUT> WL10644 - TSF2_10: not active member in addInstances throw RuntimeError.
-ERROR: The following instances cannot be added because they are not active members of the cluster: 'localhost:1111'. Please verify if the specified addresses are correct, or if the instances are currently inactive.
-
-//@<ERR> WL10644 - TSF2_10: not active member in addInstances throw RuntimeError.
-The following instances are not active members of the cluster: 'localhost:1111'. (RuntimeError)
-
-//@<OUT> WL10644 - TSF2_11: warning for already members in addInstances.
-Rescanning the cluster...
-
-Result of the rescanning operation for the 'c' cluster:
-{
-    "metadataConsistent": true,
-    "name": "c",
-    "newTopologyMode": null,
-    "newlyDiscoveredInstances": [],
-    "unavailableInstances": [],
-    "updatedInstances": []
-}
-
-WARNING: The following instances were not added to the metadata because they are already part of the cluster: '<<<member_address>>>'. Please verify if the specified value for 'addInstances' option is correct.
-
-//@<OUT> WL10644 - TSF3_10: active member in removeInstances throw RuntimeError.
-ERROR: The following instances cannot be removed because they are active members of the cluster: '<<<member_address>>>'. Please verify if the specified addresses are correct.
-
-//@<ERR> WL10644 - TSF3_10: active member in removeInstances throw RuntimeError.
-The following instances are active members of the cluster: '<<<member_address>>>'. (RuntimeError)
-
-//@<OUT> WL10644 - TSF3_11: warning for not members in removeInstances.
-Rescanning the cluster...
-
-Result of the rescanning operation for the 'c' cluster:
-{
-    "metadataConsistent": true,
-    "name": "c",
-    "newTopologyMode": null,
-    "newlyDiscoveredInstances": [],
-    "unavailableInstances": [],
-    "updatedInstances": []
-}
-
-WARNING: The following instances were not removed from the metadata because they are already not part of the cluster or are running auto-rejoin: 'localhost:1111'. Please verify if the specified value for 'removeInstances' option is correct.
-
-//@ WL10644: Duplicated values between addInstances and removeInstances.
-||The same instances cannot be used in both 'addInstances' and 'removeInstances' options: 'localhost:3300, localhost:3301'. (ArgumentError)
-||The same instance cannot be used in both 'addInstances' and 'removeInstances' options: 'localhost:3301'. (ArgumentError)
-||The same instance cannot be used in both 'addInstances' and 'removeInstances' options: 'localhost:3301'. (ArgumentError)
-
-//@<OUT> WL10644 - TSF2_1: Rescan with addInstances:[complete_valid_list].
-Rescanning the cluster...
-
-Result of the rescanning operation for the 'c' cluster:
-{
-    "metadataConsistent": true,
-    "name": "c",
-    "newTopologyMode": null,
-    "newlyDiscoveredInstances": [
-        {
-            "host": "<<<member_address2>>>",
-            "member_id": "<<<instance2_uuid>>>",
-            "name": null<<<(__version_num>=80011)?",\n[[*]]\"version\": \"" + __version + "\"":"">>>
-        },
-        {
-            "host": "<<<member_address3>>>",
-            "member_id": "<<<instance3_uuid>>>",
-            "name": null<<<(__version_num>=80011)?",\n[[*]]\"version\": \"" + __version + "\"":"">>>
-        }
-    ],
-    "unavailableInstances": [],
-    "updatedInstances": []
-}
-
-A new instance '<<<member_address2>>>' was discovered in the cluster.
-Adding instance to the cluster metadata...
-The instance '<<<member_address2>>>' was successfully added to the cluster metadata.
-
-A new instance '<<<member_address3>>>' was discovered in the cluster.
-Adding instance to the cluster metadata...
-The instance '<<<member_address3>>>' was successfully added to the cluster metadata.
-
-//@<OUT> WL10644 - TSF2_2: Rescan with addInstances:[incomplete_valid_list] and interactive:true.
-Rescanning the cluster...
-
-Result of the rescanning operation for the 'c' cluster:
-{
-    "metadataConsistent": true,
-    "name": "c",
-    "newTopologyMode": null,
-    "newlyDiscoveredInstances": [
-        {
-            "host": "<<<member_address2>>>",
-            "member_id": "<<<instance2_uuid>>>",
-            "name": null<<<(__version_num>=80011)?",\n[[*]]\"version\": \"" + __version + "\"":"">>>
-        },
-        {
-            "host": "<<<member_address3>>>",
-            "member_id": "<<<instance3_uuid>>>",
-            "name": null<<<(__version_num>=80011)?",\n[[*]]\"version\": \"" + __version + "\"":"">>>
-        }
-    ],
-    "unavailableInstances": [],
-    "updatedInstances": []
-}
-
-A new instance '<<<member_address2>>>' was discovered in the cluster.
-Adding instance to the cluster metadata...
-The instance '<<<member_address2>>>' was successfully added to the cluster metadata.
-
-A new instance '<<<member_address3>>>' was discovered in the cluster.
-Would you like to add it to the cluster metadata? [Y/n]: Adding instance to the cluster metadata...
-The instance '<<<member_address3>>>' was successfully added to the cluster metadata.
-
-//@<OUT> WL10644 - TSF2_3: Rescan with addInstances:[incomplete_valid_list] and interactive:false.
-Rescanning the cluster...
-
-Result of the rescanning operation for the 'c' cluster:
-{
-    "metadataConsistent": true,
-    "name": "c",
-    "newTopologyMode": null,
-    "newlyDiscoveredInstances": [
-        {
-            "host": "<<<member_address2>>>",
-            "member_id": "<<<instance2_uuid>>>",
-            "name": null<<<(__version_num>=80011)?",\n[[*]]\"version\": \"" + __version + "\"":"">>>
-        },
-        {
-            "host": "<<<member_address3>>>",
-            "member_id": "<<<instance3_uuid>>>",
-            "name": null<<<(__version_num>=80011)?",\n[[*]]\"version\": \"" + __version + "\"":"">>>
-        }
-    ],
-    "unavailableInstances": [],
-    "updatedInstances": []
-}
-
-A new instance '<<<member_address2>>>' was discovered in the cluster.
-Adding instance to the cluster metadata...
-The instance '<<<member_address2>>>' was successfully added to the cluster metadata.
-
-A new instance '<<<member_address3>>>' was discovered in the cluster.
-
-
-//@<OUT> WL10644 - TSF2_4: Rescan with addInstances:"auto" and interactive:true.
-Rescanning the cluster...
-
-Result of the rescanning operation for the 'c' cluster:
-{
-    "metadataConsistent": true,
-    "name": "c",
-    "newTopologyMode": null,
-    "newlyDiscoveredInstances": [
-        {
-            "host": "<<<member_address2>>>",
-            "member_id": "<<<instance2_uuid>>>",
-            "name": null<<<(__version_num>=80011)?",\n[[*]]\"version\": \"" + __version + "\"":"">>>
-        },
-        {
-            "host": "<<<member_address3>>>",
-            "member_id": "<<<instance3_uuid>>>",
-            "name": null<<<(__version_num>=80011)?",\n[[*]]\"version\": \"" + __version + "\"":"">>>
-        }
-    ],
-    "unavailableInstances": [],
-    "updatedInstances": []
-}
-
-A new instance '<<<member_address2>>>' was discovered in the cluster.
-Adding instance to the cluster metadata...
-The instance '<<<member_address2>>>' was successfully added to the cluster metadata.
-
-A new instance '<<<member_address3>>>' was discovered in the cluster.
-Adding instance to the cluster metadata...
-The instance '<<<member_address3>>>' was successfully added to the cluster metadata.
-
-
-
-//@<OUT> WL10644 - TSF2_5: Rescan with addInstances:"auto" and interactive:false.
-Rescanning the cluster...
-
-Result of the rescanning operation for the 'c' cluster:
-{
-    "metadataConsistent": true,
-    "name": "c",
-    "newTopologyMode": null,
-    "newlyDiscoveredInstances": [
-        {
-            "host": "<<<member_address2>>>",
-            "member_id": "<<<instance2_uuid>>>",
-            "name": null<<<(__version_num>=80011)?",\n[[*]]\"version\": \"" + __version + "\"":"">>>
-        },
-        {
-            "host": "<<<member_address3>>>",
-            "member_id": "<<<instance3_uuid>>>",
-            "name": null<<<(__version_num>=80011)?",\n[[*]]\"version\": \"" + __version + "\"":"">>>
-        }
-    ],
-    "unavailableInstances": [],
-    "updatedInstances": []
-}
-
-A new instance '<<<member_address2>>>' was discovered in the cluster.
-Adding instance to the cluster metadata...
-The instance '<<<member_address2>>>' was successfully added to the cluster metadata.
-
-A new instance '<<<member_address3>>>' was discovered in the cluster.
-Adding instance to the cluster metadata...
-The instance '<<<member_address3>>>' was successfully added to the cluster metadata.
-
-//@<OUT> WL10644 - TSF3_1: Rescan with removeInstances:[complete_valid_list].
+//@<OUT> Rescan with removeObsolete: true.
 Rescanning the cluster...
 
 Result of the rescanning operation for the 'c' cluster:
@@ -251,70 +43,7 @@ The instance '<<<member_address3>>>' is no longer part of the cluster.
 Removing instance from the cluster metadata...
 The instance '<<<member_address3>>>' was successfully removed from the cluster metadata.
 
-//@<OUT> WL10644 - TSF3_2: Rescan with removeInstances:[incomplete_valid_list] and interactive:true.
-Rescanning the cluster...
-
-Result of the rescanning operation for the 'c' cluster:
-{
-    "metadataConsistent": true,
-    "name": "c",
-    "newTopologyMode": null,
-    "newlyDiscoveredInstances": [],
-    "unavailableInstances": [
-        {
-            "host": "<<<member_fqdn_address2>>>",
-            "label": "<<<member_fqdn_address2>>>",
-            "member_id": "<<<instance2_uuid>>>"
-        },
-        {
-            "host": "<<<member_fqdn_address3>>>",
-            "label": "<<<member_fqdn_address3>>>",
-            "member_id": "<<<instance3_uuid>>>"
-        }
-    ],
-    "updatedInstances": []
-}
-
-The instance '<<<member_fqdn_address2>>>' is no longer part of the cluster.
-Removing instance from the cluster metadata...
-The instance '<<<member_fqdn_address2>>>' was successfully removed from the cluster metadata.
-
-The instance '<<<member_fqdn_address3>>>' is no longer part of the cluster.
-The instance is either offline or left the HA group. You can try to add it to the cluster again with the cluster.rejoinInstance('<<<member_fqdn_address3>>>') command or you can remove it from the cluster configuration.
-Would you like to remove it from the cluster metadata? [Y/n]: Removing instance from the cluster metadata...
-The instance '<<<member_fqdn_address3>>>' was successfully removed from the cluster metadata.
-
-//@<OUT> WL10644 - TSF3_3: Rescan with removeInstances:[incomplete_valid_list] and interactive:false.
-Rescanning the cluster...
-
-Result of the rescanning operation for the 'c' cluster:
-{
-    "metadataConsistent": true,
-    "name": "c",
-    "newTopologyMode": null,
-    "newlyDiscoveredInstances": [],
-    "unavailableInstances": [
-        {
-            "host": "<<<member_fqdn_address2>>>",
-            "label": "<<<member_fqdn_address2>>>",
-            "member_id": "<<<instance2_uuid>>>"
-        },
-        {
-            "host": "<<<member_fqdn_address3>>>",
-            "label": "<<<member_fqdn_address3>>>",
-            "member_id": "<<<instance3_uuid>>>"
-        }
-    ],
-    "updatedInstances": []
-}
-
-The instance '<<<member_fqdn_address2>>>' is no longer part of the cluster.
-Removing instance from the cluster metadata...
-The instance '<<<member_fqdn_address2>>>' was successfully removed from the cluster metadata.
-
-The instance '<<<member_fqdn_address3>>>' is no longer part of the cluster.
-
-//@<OUT> WL10644 - TSF3_4: Rescan with removeInstances:"auto" and interactive:true.
+//@<OUT> Rescan with removeObsolete: true and interactive:true.
 Rescanning the cluster...
 
 Result of the rescanning operation for the 'c' cluster:
@@ -346,7 +75,7 @@ The instance '<<<member_fqdn_address3>>>' is no longer part of the cluster.
 Removing instance from the cluster metadata...
 The instance '<<<member_fqdn_address3>>>' was successfully removed from the cluster metadata.
 
-//@<OUT> WL10644 - TSF3_5: Rescan with removeInstances:"auto" and interactive:false.
+//@<OUT> Rescan with removeObsolete: true and interactive:false.
 Rescanning the cluster...
 
 Result of the rescanning operation for the 'c' cluster:
@@ -375,8 +104,6 @@ Removing instance from the cluster metadata...
 The instance '<<<member_fqdn_address2>>>' was successfully removed from the cluster metadata.
 
 The instance '<<<member_fqdn_address3>>>' is no longer part of the cluster.
-Removing instance from the cluster metadata...
-The instance '<<<member_fqdn_address3>>>' was successfully removed from the cluster metadata.
 
 //@ WL10644 - TSF4_2: status() error because topology mode changed.
 ||The InnoDB Cluster topology type (Multi-Primary) does not match the current Group Replication configuration (Single-Primary). Please use <cluster>.rescan() or change the Group Replication configuration accordingly. (RuntimeError)
diff --git a/unittest/scripts/auto/js_adminapi/validation/dba_cluster_help.js b/unittest/scripts/auto/js_adminapi/validation/dba_cluster_help.js
index 37ab4c3378..17808c661a 100644
--- a/unittest/scripts/auto/js_adminapi/validation/dba_cluster_help.js
+++ b/unittest/scripts/auto/js_adminapi/validation/dba_cluster_help.js
@@ -95,13 +95,6 @@ FUNCTIONS
       routingGuidelines()
             Lists the Routing Guidelines defined for the Cluster.
 
-      routingOptions([router])
-            Lists the Cluster Routers configuration options.
-
-            ATTENTION: This function is deprecated and will be removed in a
-                       future release of MySQL Shell. Use
-                       Cluster.routerOptions() instead.
-
       setInstanceOption(instance, option, value)
             Changes the value of an option in a Cluster member.
 
@@ -1181,12 +1174,6 @@ DESCRIPTION
 
       The options dictionary may contain the following attributes:
 
-      - addInstances: List with the connection data of the new active instances
-        to add to the metadata, or "auto" to automatically add missing
-        instances to the metadata. Deprecated.
-      - removeInstances: List with the connection data of the obsolete
-        instances to remove from the metadata, or "auto" to automatically
-        remove obsolete instances from the metadata. Deprecated.
       - upgradeCommProtocol: Boolean. Set to true to upgrade the Group
         Replication communication protocol to the highest version possible.
       - updateViewChangeUuid: Boolean. Indicates if the command should generate
@@ -1202,21 +1189,11 @@ DESCRIPTION
       - repairMetadata: Boolean. Set to true to repair the Metadata if detected
         to be inconsistent.
 
-      The value for 'addInstances' and 'removeInstances' is used to specify
-      which instances to add or remove from the metadata, respectively. Both
-      options accept list connection data. In addition, the "auto" value can be
-      used for both options in order to automatically add or remove the
-      instances in the metadata, without having to explicitly specify them.
-
       'repairMetadata' is used to eliminate any inconsistencies detected in the
       Metadata. These inconsistencies may arise from a few scenarios, such as
       the failure of one or more commands. Clusters detected in the ClusterSet
       Metadata that do not qualify as valid members will be removed.
 
-      ATTENTION: The addInstances and removeInstances options will be removed
-                 in a future release. Use addUnmanaged and removeObsolete
-                 instead.
-
 //@<OUT> Status
 NAME
       status - Describe the status of the Cluster.
@@ -1636,27 +1613,6 @@ DESCRIPTION
       to a terminal, the recoveryProgress option has the value of 2. Otherwise,
       it has the value of 1.
 
-//@<OUT> routingOptions
-NAME
-      routingOptions - Lists the Cluster Routers configuration options.
-
-SYNTAX
-      <Cluster>.routingOptions([router])
-
-WHERE
-      router: Identifier of the router instance to query for the options.
-
-RETURNS
-      A JSON object describing the configuration options of all router
-      instances of the Cluster and its global options or just the given Router.
-
-DESCRIPTION
-      ATTENTION: This function is deprecated and will be removed in a future
-                 release of MySQL Shell. Use Cluster.routerOptions() instead.
-
-      This function lists the Router configuration options of all Routers of
-      the Cluster or the target Router.
-
 //@<OUT> routerOptions
 NAME
       routerOptions - Lists the configuration options of the Cluster's Routers.
@@ -1909,5 +1865,11 @@ DESCRIPTION
 
       - force (boolean): Allows overwriting an existing Routing Guideline with
         the same name. Disabled by default.
+      - rename (string): Allows importing a guideline under a new name; for
+        example, if a guideline with the same name already exists in the
+        Cluster.
+
+      The options 'force' and 'rename' are mutually exclusive. Only one can be
+      used at a time.
 
       For more information on Routing Guidelines, see \? RoutingGuideline.
diff --git a/unittest/scripts/auto/js_adminapi/validation/dba_help.js b/unittest/scripts/auto/js_adminapi/validation/dba_help.js
index 89dc69b3ba..8e58ed5565 100644
--- a/unittest/scripts/auto/js_adminapi/validation/dba_help.js
+++ b/unittest/scripts/auto/js_adminapi/validation/dba_help.js
@@ -844,6 +844,8 @@ DESCRIPTION
         address pattern (default: %).
       - ignoreSslError: ignore errors when adding SSL support for the new
         instance, by default: true.
+      - mysqldPath: path to the mysqld binary, or the MySQL installation root.
+        Used to locate the server binary for sandbox deployment.
       - mysqldOptions: list of MySQL configuration options to write to the
         my.cnf file, as option=value strings.
 
diff --git a/unittest/scripts/auto/js_adminapi/validation/routingguideline_help.js b/unittest/scripts/auto/js_adminapi/validation/routingguideline_help.js
index ca967a1a7e..8c962fe736 100644
--- a/unittest/scripts/auto/js_adminapi/validation/routingguideline_help.js
+++ b/unittest/scripts/auto/js_adminapi/validation/routingguideline_help.js
@@ -153,8 +153,8 @@ FUNCTIONS
       removeRoute(name)
             Remove a route from the Routing Guideline.
 
-      rename()
-            Rename the target Routing Guideline.
+      rename(name)
+            Renames the target Routing Guideline.
 
       routes()
             List routes defined for the Routing Guideline.
@@ -276,7 +276,7 @@ DESCRIPTION
       - $.router.localCluster: Name of the cluster configured as local to the
         Router.
       - $.router.hostname: Hostname where the Router is running.
-      - $.router.address: IP address on which the Router is listening.
+      - $.router.bindAddress: IP address on which the Router is listening.
       - $.router.tags: A key-value object containing user-defined tags stored
         in the topology's metadata for that Router instance.
       - $.router.routeName: Name of the Routing plugin used by the Router.
@@ -351,7 +351,7 @@ DESCRIPTION
       - $.router.localCluster: Name of the cluster configured as local to the
         Router.
       - $.router.hostname: Hostname where Router is running.
-      - $.router.address: IP address on which the Router is listening.
+      - $.router.bindAddress: IP address on which the Router is listening.
       - $.router.tags: A key-value object containing user-defined tags stored
         in the topology's metadata for that Router instance.
       - $.router.routeName: Name of the Routing plugin used by the Router.
@@ -466,9 +466,9 @@ DESCRIPTION
       routes. It also identifies all destinations not referenced by any route.
 
       Destinations that rely on expressions involving the target router port or
-      address (e.g., '$.router.port' or '$.router.address') may produce results
-      that differ from actual behavior in MySQL Router. Since this function
-      evaluates the guideline outside the context of the router, these
+      address (e.g., '$.router.port' or '$.router.bindAddress') may produce
+      results that differ from actual behavior in MySQL Router. Since this
+      function evaluates the guideline outside the context of the router, these
       expressions are not matched against live router runtime data and may not
       reflect the router's operational reality. On that case, the option
       'router' must be set to specify the Router instance that should be used
@@ -480,15 +480,19 @@ DESCRIPTION
 
 //@<OUT> rename
 NAME
-      rename - Rename the target Routing Guideline.
+      rename - Renames the target Routing Guideline.
 
 SYNTAX
-      <RoutingGuideline>.rename()
+      <RoutingGuideline>.rename(name)
 
-DESCRIPTION
-      This functions allows changing the name of an existing Routing Guideline.
+WHERE
+      name: The new name to assign to the Routing Guideline.
+
+RETURNS
+      Nothing.
 
-      @returns Nothing
+DESCRIPTION
+      This command renames the target Routing Guideline.
 
 //@<OUT> setDestinationOption
 NAME
diff --git a/unittest/scripts/auto/js_adminapi_async/scripts/add_instance.js b/unittest/scripts/auto/js_adminapi_async/scripts/add_instance.js
index 6a8bd32765..4544a01af1 100644
--- a/unittest/scripts/auto/js_adminapi_async/scripts/add_instance.js
+++ b/unittest/scripts/auto/js_adminapi_async/scripts/add_instance.js
@@ -538,7 +538,7 @@ var bug_30632029 = [
     "RESET REPLICA ALL FOR CHANNEL ''"
 ];
 
-shell.options['dba.logSql'] = 2
+shell.options['logSql'] = "unfiltered"
 WIPE_SHELL_LOG();
 
 shell.options.useWizards = true;
@@ -549,7 +549,7 @@ EXPECT_SHELL_LOG_CONTAINS(bug_30632029[0]);
 EXPECT_SHELL_LOG_CONTAINS(bug_30632029[1]);
 
 WIPE_SHELL_LOG();
-shell.options['dba.logSql'] = 0
+shell.options['logSql'] = "off"
 
 //@ BUG#30281908: add instance using clone and simulating a restart timeout {VER(>= 8.0.17)}
 rs.removeInstance(__sandbox3);
diff --git a/unittest/scripts/auto/js_adminapi_async/scripts/force_primary_instance.js b/unittest/scripts/auto/js_adminapi_async/scripts/force_primary_instance.js
index fb7a45f163..de4f1c29da 100644
--- a/unittest/scripts/auto/js_adminapi_async/scripts/force_primary_instance.js
+++ b/unittest/scripts/auto/js_adminapi_async/scripts/force_primary_instance.js
@@ -181,14 +181,12 @@ testutil.waitForReplConnectionError(__mysql_sandbox_port2, "");
 var before = rs.status();
 println("PRIMARY", before.replicaSet.primary);
 
-begin_dba_log_sql();
+WIPE_SHELL_LOG();
+\option logSql = on
 
 rs.forcePrimaryInstance(__sandbox2, {dryRun:true});
 
-var logs = end_dba_log_sql();
-EXPECT_NO_SQL(__sandbox1, logs);
-EXPECT_NO_SQL(__sandbox2, logs);
-EXPECT_NO_SQL(__sandbox3, logs);
+\option --unset logSql
 
 EXPECT_EQ(before.replicaSet.primary, rs.status().replicaSet.primary);
 
diff --git a/unittest/scripts/auto/js_adminapi_async/scripts/rejoin_instance.js b/unittest/scripts/auto/js_adminapi_async/scripts/rejoin_instance.js
index 346ac8a29c..8f3bff87ea 100644
--- a/unittest/scripts/auto/js_adminapi_async/scripts/rejoin_instance.js
+++ b/unittest/scripts/auto/js_adminapi_async/scripts/rejoin_instance.js
@@ -82,7 +82,7 @@ EXPECT_THROWS(function() {
 }, "Invalid value for cloneDonor: Invalid address format in '::1'. Must be <host>:<port> or [<ip>]:<port> for IPv6 addresses");
 
 //@<> Try rejoin ONLINE instance (fail).
-shell.options["dba.logSql"] = 1;
+shell.options["logSql"] = "on";
 WIPE_SHELL_LOG();
 
 EXPECT_NO_THROWS(function() { rs.rejoinInstance(__sandbox2); });
@@ -362,7 +362,7 @@ var bug_30632029 = [
 ];
 
 //@<> BUG#30632029: add instance using clone and a secondary as donor rest
-\option dba.logSql = 2
+\option logSql = unfiltered
 WIPE_SHELL_LOG();
 
 shell.options.useWizards = true;
diff --git a/unittest/scripts/auto/js_adminapi_async/scripts/repl_options_async.js b/unittest/scripts/auto/js_adminapi_async/scripts/repl_options_async.js
index c71aa39519..ea8efb6ff4 100644
--- a/unittest/scripts/auto/js_adminapi_async/scripts/repl_options_async.js
+++ b/unittest/scripts/auto/js_adminapi_async/scripts/repl_options_async.js
@@ -126,11 +126,11 @@ EXPECT_THROWS(function() {
 // fails because of network related options
 WIPE_SHELL_LOG();
 
-begin_dba_log_sql();
+shell.options["logSql"] = "unfiltered"
 EXPECT_THROWS(function() {
     rs.addInstance(__sandbox_uri2, {replicationConnectRetry: 10, replicationRetryCount: 11, replicationHeartbeatPeriod: 33.3, replicationCompressionAlgorithms: "zlib", replicationZstdCompressionLevel: "4", replicationBind: "foo", replicationNetworkNamespace: "bar"});
 }, (__version_num >= 80400) ? "Error found in replication receiver thread" : "Replication thread not in expected state");
-end_dba_log_sql();
+shell.options["logSql"] = "off"
 
 EXPECT_SHELL_LOG_CONTAINS("SOURCE_CONNECT_RETRY=10");
 EXPECT_SHELL_LOG_CONTAINS("SOURCE_RETRY_COUNT=11");
@@ -140,6 +140,22 @@ EXPECT_SHELL_LOG_CONTAINS("SOURCE_ZSTD_COMPRESSION_LEVEL=4");
 EXPECT_SHELL_LOG_CONTAINS("SOURCE_BIND='foo'");
 EXPECT_SHELL_LOG_CONTAINS("NETWORK_NAMESPACE='bar'");
 
+WIPE_SHELL_LOG();
+
+shell.options["logSql"] = "on"
+EXPECT_THROWS(function() {
+    rs.addInstance(__sandbox_uri2, {replicationConnectRetry: 10, replicationRetryCount: 11, replicationHeartbeatPeriod: 33.3, replicationCompressionAlgorithms: "zlib", replicationZstdCompressionLevel: "4", replicationBind: "foo", replicationNetworkNamespace: "bar"});
+}, (__version_num >= 80400) ? "Error found in replication receiver thread" : "Replication thread not in expected state");
+shell.options["logSql"] = "off"
+
+EXPECT_SHELL_LOG_NOT_CONTAINS("SOURCE_CONNECT_RETRY=10");
+EXPECT_SHELL_LOG_NOT_CONTAINS("SOURCE_RETRY_COUNT=11");
+EXPECT_SHELL_LOG_NOT_CONTAINS("SOURCE_HEARTBEAT_PERIOD=33.3");
+EXPECT_SHELL_LOG_NOT_CONTAINS("SOURCE_COMPRESSION_ALGORITHMS='zlib'");
+EXPECT_SHELL_LOG_NOT_CONTAINS("SOURCE_ZSTD_COMPRESSION_LEVEL=4");
+EXPECT_SHELL_LOG_NOT_CONTAINS("SOURCE_BIND='foo'");
+EXPECT_SHELL_LOG_NOT_CONTAINS("NETWORK_NAMESPACE='bar'");
+
 // must succeed
 EXPECT_NO_THROWS(function() { rs.addInstance(__sandbox_uri2, {replicationConnectRetry: 20, replicationRetryCount: 22, replicationHeartbeatPeriod: 44.4, replicationCompressionAlgorithms: "zstd", replicationZstdCompressionLevel: "7"}); });
 
@@ -156,9 +172,9 @@ reset_instance(session2);
 //@<> FR9.1 / FR9.2 check addInstance options (don't use hard-coded values: if the server updates the defaults the test stops working)
 WIPE_SHELL_LOG();
 
-begin_dba_log_sql();
+\option logSql = on
 EXPECT_NO_THROWS(function() { rs.addInstance(__sandbox_uri2); });
-end_dba_log_sql();
+\option --unset logSql
 
 // "source_connect_retry" and "source_retry_count" are always present because they're used in the connectivity tests
 EXPECT_SHELL_LOG_NOT_CONTAINS("SOURCE_HEARTBEAT_PERIOD");
@@ -285,9 +301,9 @@ check_repl_option_metadata_only(session, session2, "opt_replZstdCompressionLevel
 
 WIPE_SHELL_LOG();
 
-begin_dba_log_sql();
+\option logSql = on
 EXPECT_NO_THROWS(function() { rs.rejoinInstance(__sandbox_uri2); });
-end_dba_log_sql();
+\option --unset logSql
 
 //because we added a NULL option, the channel should have been reset
 EXPECT_SHELL_LOG_CONTAINS("RESET REPLICA ALL FOR CHANNEL ''");
@@ -304,7 +320,7 @@ EXPECT_NO_THROWS(function() { rs.rejoinInstance(__sandbox_uri2); });
 EXPECT_OUTPUT_CONTAINS(`The instance '${hostname}:${__mysql_sandbox_port2}' is ONLINE and replicating from '${hostname}:${__mysql_sandbox_port1}'.`)
 
 //@<> FR11.4 simple change, no reset of the channel needed
-shell.options["dba.logSql"] = 1;
+shell.options["logSql"] = "on";
 WIPE_SHELL_LOG();
 
 EXPECT_NO_THROWS(function() { rs.setInstanceOption(__sandbox_uri2, "replicationRetryCount", 17); });
diff --git a/unittest/scripts/auto/js_adminapi_async/scripts/router_management.js b/unittest/scripts/auto/js_adminapi_async/scripts/router_management.js
index 4d273bae25..e21b0a3f5d 100644
--- a/unittest/scripts/auto/js_adminapi_async/scripts/router_management.js
+++ b/unittest/scripts/auto/js_adminapi_async/scripts/router_management.js
@@ -21,66 +21,81 @@ session.runSql("INSERT mysql_innodb_cluster_metadata.routers VALUES (3, 'another
 var cr_router3 = "routerhost2::";
 session.runSql("INSERT mysql_innodb_cluster_metadata.routers VALUES (4, '', 'mysqlrouter', 'routerhost2', '8.1.0', '2023-04-26 11:22:33', '{\"bootstrapTargetType\": \"cluster\"}', ?, NULL, NULL)", [cluster_id]);
 
-//@<> rs.routingOptions on invalid router
-EXPECT_THROWS(function(){ rs.routingOptions("invalid_router"); }, "Router 'invalid_router' is not registered in the replicaset");
+//@<> rs.routerOptions on invalid router
+EXPECT_THROWS(function(){ rs.routerOptions({"router":"invalid_router"}); }, "Router 'invalid_router' is not registered in the replicaset");
 
-//@<> rs.routingOptions() with all defaults
-rs.routingOptions();
+//@<> rs.routerOptions() with all defaults
+rs.routerOptions();
 EXPECT_OUTPUT_CONTAINS_MULTILINE(`
 {
-    "global": {
-        "guideline": null,
-        "stats_updates_frequency": null,
-        "tags": {}
+    "configuration": {
+        "routing_rules": {
+            "tags": {}
+        }
     },
+    "name": "rs",
     "routers": {
-        "routerhost1::system": {},
-        "routerhost2::": {},
-        "routerhost2::another": {},
-        "routerhost2::system": {}
+        "routerhost1::system": {
+            "configuration": {}
+        },
+        "routerhost2::": {
+            "configuration": {}
+        },
+        "routerhost2::another": {
+            "configuration": {}
+        },
+        "routerhost2::system": {
+            "configuration": {}
+        }
     }
 }
 `);
 
 //@<> rs.setRoutingOption, all valid values, tag
 rs.setRoutingOption(router1, "tag:test_tag", 567);
-EXPECT_JSON_EQ(567, rs.routingOptions(router1)[router1]["tags"]["test_tag"]);
-EXPECT_JSON_EQ(567, rs.routingOptions()["routers"][router1]["tags"]["test_tag"]);
+EXPECT_JSON_EQ(567, rs.routerOptions({"router": router1, "extended": 2})["routers"][router1]["configuration"]["routing_rules"]["tags"]["test_tag"]);
+EXPECT_JSON_EQ(567, rs.routerOptions({"extended": 2})["routers"][router1]["configuration"]["routing_rules"]["tags"]["test_tag"]);
 
 rs.setRoutingOption("tag:test_tag", 567);
-EXPECT_JSON_EQ(567, rs.routingOptions()["global"]["tags"]["test_tag"]);
+EXPECT_JSON_EQ(567, rs.routerOptions()["configuration"]["routing_rules"]["tags"]["test_tag"]);
 
 rs.setRoutingOption("tag:test_tag", null);
 rs.setRoutingOption(router1, "tag:test_tag", null);
-EXPECT_JSON_EQ(null, rs.routingOptions()["routers"][router1]["tags"]["test_tag"]);
-EXPECT_JSON_EQ(null, rs.routingOptions()["global"]["tags"]["test_tag"]);
+EXPECT_JSON_EQ({}, rs.routerOptions({"extended": 2})["routers"][router1]["configuration"]["routing_rules"]["tags"]);
+EXPECT_JSON_EQ({}, rs.routerOptions()["configuration"]["routing_rules"]["tags"]);
+
+rs.setRoutingOption("tag:test_tag", 567);
+rs.setRoutingOption(router1, "tag:test_tag", 567);
 
 rs.setRoutingOption("tags", null);
 rs.setRoutingOption(router1, "tags", null);
-EXPECT_JSON_EQ(undefined, rs.routingOptions()["routers"][router1]["tags"]);
-EXPECT_JSON_EQ({}, rs.routingOptions()["global"]["tags"]);
+EXPECT_JSON_EQ({}, rs.routerOptions({"extended": 2})["routers"][router1]["configuration"]["routing_rules"]["tags"]);
+EXPECT_JSON_EQ({}, rs.routerOptions()["configuration"]["routing_rules"]["tags"]);
 
 //@<> rs.setRoutingOption, all valid values
 function CHECK_SET_ROUTING_OPTION(option, value, expected_value) {
-  orig_options = rs.routingOptions();
+  orig_options = rs.routerOptions({"extended": 2});
 
-  router_options = rs.routingOptions(router1);
-  global_options = rs.routingOptions();
+  router_options = rs.routerOptions({"router": router1, "extended": 2});
+  global_options = rs.routerOptions({"extended": 2});
 
   rs.setRoutingOption(router1, option, value);
-  router_options[router1][option] = expected_value;
-  global_options["routers"][router1][option] = expected_value;
-  EXPECT_JSON_EQ(router_options, rs.routingOptions(router1), "router check");
-  EXPECT_JSON_EQ(global_options, rs.routingOptions(), "router check 2");
+  router_options["routers"][router1]["configuration"]["routing_rules"][option] = expected_value;
+  global_options["routers"][router1]["configuration"]["routing_rules"][option] = expected_value;
+  EXPECT_JSON_EQ(router_options, rs.routerOptions({"router": router1, "extended": 2}), "router check");
+  EXPECT_JSON_EQ(global_options, rs.routerOptions({"extended": 2}), "router check 2");
 
   rs.setRoutingOption(option, value);
-  global_options["global"][option] = expected_value;
-  EXPECT_JSON_EQ(global_options, rs.routingOptions(), "global check");
+  global_options["configuration"]["routing_rules"][option] = expected_value;
+  global_options["routers"][router2]["configuration"]["routing_rules"][option] = expected_value;
+  global_options["routers"][router3]["configuration"]["routing_rules"][option] = expected_value;
+  global_options["routers"][cr_router3]["configuration"]["routing_rules"][option] = expected_value;
+  EXPECT_JSON_EQ(global_options, rs.routerOptions({"extended": 2}), "global check");
 
   // setting option to null should reset to default
   rs.setRoutingOption(option, null);
   rs.setRoutingOption(router1, option, null);
-  EXPECT_JSON_EQ(orig_options, rs.routingOptions(), "original check");
+  EXPECT_JSON_EQ(orig_options, rs.routerOptions({"extended": 2}), "original check");
 }
 
 CHECK_SET_ROUTING_OPTION("stats_updates_frequency", 24, 24);
@@ -88,16 +103,24 @@ CHECK_SET_ROUTING_OPTION("stats_updates_frequency", 24, 24);
 CHECK_SET_ROUTING_OPTION('tags', {}, {});
 CHECK_SET_ROUTING_OPTION('tags', { "a": 123 }, { "a": 123 });
 
-//@<> default values filled in when metadata is missing some option (e.g. upgrade)
-var full_options = rs.routingOptions();
+//@<> default values are not filled in when metadata is missing some option (e.g. upgrade)
+var full_options = rs.routerOptions({"extended": 2});
+
+EXPECT_EQ(full_options["configuration"]["routing_rules"].hasOwnProperty("tags"),true)
 
 var router_options = session.runSql("select router_options from mysql_innodb_cluster_metadata.clusters").fetchOne()[0]
 session.runSql("update mysql_innodb_cluster_metadata.clusters set router_options='{}'");
 
-EXPECT_JSON_EQ(full_options, rs.routingOptions());
+EXPECT_EQ(rs.routerOptions()["configuration"]["routing_rules"].hasOwnProperty("tags"),false)
+
+EXPECT_JSON_EQ({}, rs.routerOptions()["configuration"]["routing_rules"])
+EXPECT_JSON_EQ({}, rs.routerOptions({"extended": 2})["routers"][router1]["configuration"]["routing_rules"])
+EXPECT_JSON_EQ({}, rs.routerOptions({"extended": 2})["routers"][router2]["configuration"]["routing_rules"])
+EXPECT_JSON_EQ({}, rs.routerOptions({"extended": 2})["routers"][router3]["configuration"]["routing_rules"])
+EXPECT_JSON_EQ({}, rs.routerOptions({"extended": 2})["routers"][cr_router3]["configuration"]["routing_rules"])
 
 session.runSql("update mysql_innodb_cluster_metadata.clusters set router_options=?", [router_options]);
-EXPECT_JSON_EQ(full_options, rs.routingOptions());
+EXPECT_JSON_EQ(full_options, rs.routerOptions({"extended": 2}));
 
 //@<> reset option
 rs.setRoutingOption("stats_updates_frequency", 33);
@@ -120,18 +143,18 @@ EXPECT_FALSE("stats_updates_frequency" in options);
 rs.setRoutingOption("tags", {"old":"oldvalue"});
 rs.setRoutingOption("tag:test_tag", 1234);
 rs.setRoutingOption("tag:bla", "test");
-EXPECT_JSON_EQ({"old":"oldvalue", "test_tag":1234, "bla": "test"}, rs.routingOptions()["global"]["tags"]);
+EXPECT_JSON_EQ({"old":"oldvalue", "test_tag":1234, "bla": "test"}, rs.routerOptions()["configuration"]["routing_rules"]["tags"]);
 
 rs.setRoutingOption("tags", {});
-EXPECT_JSON_EQ({}, rs.routingOptions()["global"]["tags"]);
+EXPECT_JSON_EQ({}, rs.routerOptions()["configuration"]["routing_rules"]["tags"]);
 
 rs.setRoutingOption(router1, "tags", {"old":"oldvalue"});
 rs.setRoutingOption(router1, "tag:test_tag", 1234);
 rs.setRoutingOption(router1, "tag:bla", "test");
-EXPECT_JSON_EQ({"old":"oldvalue", "test_tag":1234, "bla": "test"}, rs.routingOptions()["routers"][router1]["tags"]);
+EXPECT_JSON_EQ({"old":"oldvalue", "test_tag":1234, "bla": "test"}, rs.routerOptions({"extended": "2"})["routers"][router1]["configuration"]["routing_rules"]["tags"]);
 
 rs.setRoutingOption(router1, "tags", {});
-EXPECT_JSON_EQ({}, rs.routingOptions()["routers"][router1]["tags"]);
+EXPECT_JSON_EQ({}, rs.routerOptions({"extended": 2})["routers"][router1]["configuration"]["routing_rules"]["tags"]);
 
 //@<> rs.setRoutingOption with stats_updates_frequency, invalid values
 EXPECT_THROWS(function(){ rs.setRoutingOption(router2, "stats_updates_frequency", "asda"); },
@@ -153,8 +176,8 @@ EXPECT_THROWS(function(){ rs.setRoutingOption("::system", 'stats_updates_frequen
 //@<> check types of replicaset router option values
 rs.setRoutingOption("stats_updates_frequency", 46);
 
-options = rs.routingOptions();
-EXPECT_EQ("number", typeof options["global"]["stats_updates_frequency"]);
+options = rs.routerOptions();
+EXPECT_EQ("number", typeof options["configuration"]["routing_rules"]["stats_updates_frequency"]);
 
 options = JSON.parse(session.runSql("select router_options from mysql_innodb_cluster_metadata.clusters").fetchOne()[0]);
 EXPECT_EQ("number", typeof options["stats_updates_frequency"]);
diff --git a/unittest/scripts/auto/js_adminapi_async/scripts/rs_help.js b/unittest/scripts/auto/js_adminapi_async/scripts/rs_help.js
index 7f9ddd181c..e4ef7970a2 100644
--- a/unittest/scripts/auto/js_adminapi_async/scripts/rs_help.js
+++ b/unittest/scripts/auto/js_adminapi_async/scripts/rs_help.js
@@ -107,12 +107,6 @@ rs.help("rescan");
 //@ Rescan \? [USE:Rescan]
 \? ReplicaSet.rescan
 
-//@ routingOptions
-rs.help("routingOptions")
-
-//@ routingOptions. \? [USE:routingOptions]
-\? ReplicaSet.routingOptions
-
 //@ routerOptions
 rs.help("routerOptions")
 
diff --git a/unittest/scripts/auto/js_adminapi_async/scripts/set_primary_instance.js b/unittest/scripts/auto/js_adminapi_async/scripts/set_primary_instance.js
index b890859c86..eb8dda9628 100644
--- a/unittest/scripts/auto/js_adminapi_async/scripts/set_primary_instance.js
+++ b/unittest/scripts/auto/js_adminapi_async/scripts/set_primary_instance.js
@@ -132,12 +132,12 @@ EXPECT_OUTPUT_CONTAINS(`${hostname_ip}:${__mysql_sandbox_port2} was promoted to
 WIPE_OUTPUT();
 WIPE_SHELL_LOG();
 
-shell.options["dba.logSql"] = 2;
+shell.options["logSql"] = "unfiltered";
 
 rs.setPrimaryInstance(__sandbox1, {dryRun:true});
 EXPECT_SHELL_LOG_NOT_CONTAINS("FLUSH TABLES WITH READ LOCK");
 
-shell.options["dba.logSql"] = 0;
+shell.options["logSql"] = "off";
 
 //@ timeout (should fail)
 rs.setPrimaryInstance(__sandbox1);
diff --git a/unittest/scripts/auto/js_adminapi_async/scripts/simple_openid_connect_norecord.js b/unittest/scripts/auto/js_adminapi_async/scripts/simple_openid_connect_norecord.js
index 88c14fa80a..4ed28928b2 100644
--- a/unittest/scripts/auto/js_adminapi_async/scripts/simple_openid_connect_norecord.js
+++ b/unittest/scripts/auto/js_adminapi_async/scripts/simple_openid_connect_norecord.js
@@ -67,7 +67,7 @@ session3 = mysql.getSession(__sandbox_uri3);
 EXPECT_NO_THROWS(function() {
     const sessions = [session1, session2, session3];
     sessions.forEach(session => {
-        session.runSql(`SET PERSIST authentication_openid_connect_configuration='file://${cnfg_path}'`);
+        session.runSql('SET PERSIST authentication_openid_connect_configuration=?', [`file://${cnfg_path}`]);
         session.runSql("SET sql_log_bin = 0");
         session.runSql(`CREATE USER '${OPENID_CONNECT_USER}' IDENTIFIED WITH authentication_openid_connect BY '{"identity_provider":"${OPENID_CONNECT_PROVIDER}","user":"${OPENID_CONNECT_SUBJECT}"}';`);
         session.runSql(`GRANT ALL PRIVILEGES ON *.* TO '${OPENID_CONNECT_USER}' WITH GRANT OPTION;`);
diff --git a/unittest/scripts/auto/js_adminapi_async/scripts/ssl_auth_async.js b/unittest/scripts/auto/js_adminapi_async/scripts/ssl_auth_async.js
index e21160d2a1..73ce592f86 100644
--- a/unittest/scripts/auto/js_adminapi_async/scripts/ssl_auth_async.js
+++ b/unittest/scripts/auto/js_adminapi_async/scripts/ssl_auth_async.js
@@ -214,7 +214,8 @@ EXPECT_THROWS(function() {
 }, "Invalid value for 'certSubject' option. Value cannot be an empty string.");
 
 shell.options.verbose=1
-shell.options['dba.logSql']=1
+shell.options['logSql']='on'
+
 EXPECT_NO_THROWS(function() { rset.addInstance(__sandbox_uri2); });
 shell.options.verbose=0
 EXPECT_OUTPUT_CONTAINS("* Checking connectivity and SSL configuration...");
diff --git a/unittest/scripts/auto/js_adminapi_async/validation/rs_help.js b/unittest/scripts/auto/js_adminapi_async/validation/rs_help.js
index 8e04eaea46..192913c18f 100644
--- a/unittest/scripts/auto/js_adminapi_async/validation/rs_help.js
+++ b/unittest/scripts/auto/js_adminapi_async/validation/rs_help.js
@@ -72,13 +72,6 @@ FUNCTIONS
       routingGuidelines()
             Lists the Routing Guidelines defined for the ReplicaSet.
 
-      routingOptions([router])
-            Lists the ReplicaSet Routers configuration options.
-
-            ATTENTION: This function is deprecated and will be removed in a
-                       future release of MySQL Shell. Use
-                       ReplicaSet.routerOptions() instead.
-
       setInstanceOption(instance, option, value)
             Changes the value of an option in a ReplicaSet member.
 
@@ -724,29 +717,6 @@ DESCRIPTION
       - removeObsolete: if true, all obsolete instances will be automatically
         removed from the metadata. Defaults to false.
 
-//@<OUT> routingOptions
-NAME
-      routingOptions - Lists the ReplicaSet Routers configuration options.
-
-SYNTAX
-      <ReplicaSet>.routingOptions([router])
-
-WHERE
-      router: Identifier of the router instance to query for the options.
-
-RETURNS
-      A JSON object describing the configuration options of all router
-      instances of the ReplicaSet and its global options or just the given
-      Router.
-
-DESCRIPTION
-      ATTENTION: This function is deprecated and will be removed in a future
-                 release of MySQL Shell. Use ReplicaSet.routerOptions()
-                 instead.
-
-      This function lists the Router configuration options of all Routers of
-      the ReplicaSet or the target Router.
-
 //@<OUT> routerOptions
 NAME
       routerOptions - Lists the configuration options of the ReplicaSet's
@@ -1173,5 +1143,11 @@ DESCRIPTION
 
       - force (boolean): Allows overwriting an existing Routing Guideline with
         the same name. Disabled by default.
+      - rename (string): Allows importing a guideline under a new name; for
+        example, if a guideline with the same name already exists in the
+        ReplicaSet.
+
+      The options 'force' and 'rename' are mutually exclusive. Only one can be
+      used at a time.
 
       For more information on Routing Guidelines, see \? RoutingGuideline.
diff --git a/unittest/scripts/auto/js_adminapi_clusterset/scripts/cluster_fencing_ops.js b/unittest/scripts/auto/js_adminapi_clusterset/scripts/cluster_fencing_ops.js
index b427c746ca..1346c9e5c8 100644
--- a/unittest/scripts/auto/js_adminapi_clusterset/scripts/cluster_fencing_ops.js
+++ b/unittest/scripts/auto/js_adminapi_clusterset/scripts/cluster_fencing_ops.js
@@ -171,7 +171,7 @@ EXPECT_NO_THROWS(function() { cluster.options(); });
 EXPECT_NO_THROWS(function() { cluster.status(); });
 EXPECT_NO_THROWS(function() { dba.getCluster(); });
 // ClusterSet ops
-EXPECT_NO_THROWS(function() { cs.routingOptions(); });
+EXPECT_NO_THROWS(function() { cs.routerOptions(); });
 EXPECT_NO_THROWS(function() { cs.listRouters(); });
 EXPECT_NO_THROWS(function() { cs.status(); });
 EXPECT_NO_THROWS(function() { cs.describe(); });
diff --git a/unittest/scripts/auto/js_adminapi_clusterset/scripts/cluster_ops.js b/unittest/scripts/auto/js_adminapi_clusterset/scripts/cluster_ops.js
index 66e69016ea..89c7bd9c44 100644
--- a/unittest/scripts/auto/js_adminapi_clusterset/scripts/cluster_ops.js
+++ b/unittest/scripts/auto/js_adminapi_clusterset/scripts/cluster_ops.js
@@ -223,7 +223,7 @@ CHECK_PRIMARY_CLUSTER([__sandbox_uri1, __sandbox_uri2], cluster);
 CHECK_REPLICA_CLUSTER([__sandbox_uri4, __sandbox_uri3], cluster, replicacluster);
 
 //@<> Changing the primary instance of the REPLICA Cluster to the same primary must ensure the replication stream is not broken
-\option dba.logSql = 2
+\option logSql = unfiltered
 WIPE_SHELL_LOG();
 
 replicacluster.setPrimaryInstance(__sandbox_uri4);
@@ -238,13 +238,13 @@ EXPECT_SHELL_LOG_NOT_CONTAINS(set_primary_instance_sql[0]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(set_primary_instance_sql[1]);
 EXPECT_SHELL_LOG_NOT_CONTAINS(set_primary_instance_sql[2]);
 
-\option dba.logSql = 0
+\option logSql = off
 
 CHECK_PRIMARY_CLUSTER([__sandbox_uri1, __sandbox_uri2], cluster);
 CHECK_REPLICA_CLUSTER([__sandbox_uri4, __sandbox_uri3], cluster, replicacluster);
 
 //@<> Changing the primary instance of the REPLICA Cluster must ensure the replication stream is kept
-\option dba.logSql = 2
+\option logSql = unfiltered
 WIPE_SHELL_LOG();
 
 replicacluster.setPrimaryInstance(__sandbox_uri3);
@@ -262,7 +262,7 @@ EXPECT_SHELL_LOG_CONTAINS(set_primary_instance_sql[1]);
 // that's handled by GR
 EXPECT_SHELL_LOG_NOT_CONTAINS(set_primary_instance_sql[2]);
 
-\option dba.logSql = 0
+\option logSql = off
 
 CHECK_PRIMARY_CLUSTER([__sandbox_uri1, __sandbox_uri2], cluster);
 CHECK_REPLICA_CLUSTER([__sandbox_uri3, __sandbox_uri4], cluster, replicacluster);
diff --git a/unittest/scripts/auto/js_adminapi_clusterset/scripts/create_replica_cluster.js b/unittest/scripts/auto/js_adminapi_clusterset/scripts/create_replica_cluster.js
index 4e3f5deb81..abce90f3ba 100644
--- a/unittest/scripts/auto/js_adminapi_clusterset/scripts/create_replica_cluster.js
+++ b/unittest/scripts/auto/js_adminapi_clusterset/scripts/create_replica_cluster.js
@@ -131,14 +131,14 @@ EXPECT_OUTPUT_CONTAINS(`Replica Cluster 'myReplicaCluster2' successfully created
 EXPECT_OUTPUT_CONTAINS(`dryRun finished.`);
 
 //@<> Success
-\option dba.logSql = 2
+\option logSql = unfiltered
 WIPE_SHELL_LOG();
 
 EXPECT_NO_THROWS(function() { replica = cluster_set.createReplicaCluster(__sandbox_uri3, "cluster2"); });
 
 //BUG#34465006: createReplicaCluster() must reset member actions to avoid upgrade scenario bug
 EXPECT_SHELL_LOG_CONTAINS("SELECT group_replication_reset_member_actions()");
-\option dba.logSql = 0
+\option logSql = off
 
 CHECK_REPLICA_CLUSTER([__sandbox_uri3], scene.cluster, replica);
 
diff --git a/unittest/scripts/auto/js_adminapi_clusterset/scripts/cs_help.js b/unittest/scripts/auto/js_adminapi_clusterset/scripts/cs_help.js
index 900b780670..50fdaf8ffd 100644
--- a/unittest/scripts/auto/js_adminapi_clusterset/scripts/cs_help.js
+++ b/unittest/scripts/auto/js_adminapi_clusterset/scripts/cs_help.js
@@ -79,12 +79,6 @@ cs.help("listRouters");
 //@ listRouters \? [USE:listRouters]
 \? ClusterSet.listRouters
 
-//@ routingOptions
-cs.help("routingOptions");
-
-//@ routingOptions \? [USE:routingOptions]
-\? ClusterSet.routingOptions
-
 //@ routerOptions
 cs.help("routerOptions");
 
diff --git a/unittest/scripts/auto/js_adminapi_clusterset/scripts/cs_router_management.js b/unittest/scripts/auto/js_adminapi_clusterset/scripts/cs_router_management.js
index 26a6dea703..f75ef545d4 100644
--- a/unittest/scripts/auto/js_adminapi_clusterset/scripts/cs_router_management.js
+++ b/unittest/scripts/auto/js_adminapi_clusterset/scripts/cs_router_management.js
@@ -34,51 +34,57 @@ var cr_router2 = "routerhost2::another";
 session.runSql("INSERT mysql_innodb_cluster_metadata.routers VALUES (4, '', 'mysqlrouter', 'routerhost2', '8.0.27', '2021-01-01 11:22:33', '{\"bootstrapTargetType\": \"clusterset\"}', NULL, NULL, ?)", [clusterset_id]);
 var cr_router3 = "routerhost2::";
 
-//@<> clusterset.routingOptions on invalid router
-EXPECT_THROWS(function(){ clusterset.routingOptions("invalid_router"); }, "Router 'invalid_router' is not registered in the clusterset");
+//@<> clusterset.routerOptions on invalid router
+EXPECT_THROWS(function(){ clusterset.routerOptions({"router": "invalid_router"}); }, "Router 'invalid_router' is not registered in the clusterset");
 
-//@ clusterset.routingOptions() with all defaults
-values = clusterset.routingOptions();
+//@ clusterset.routerOptions() with all defaults
+values = clusterset.routerOptions();
 
 //@<> clusterset.setRoutingOption, all valid values, tag
 clusterset.setRoutingOption(cm_router, "tag:test_tag", 567);
-EXPECT_JSON_EQ(567, clusterset.routingOptions(cm_router)[cm_router]["tags"]["test_tag"]);
-EXPECT_JSON_EQ(567, clusterset.routingOptions()["routers"][cm_router]["tags"]["test_tag"]);
+EXPECT_JSON_EQ(567, clusterset.routerOptions({"router": cm_router, "extended": 2})["routers"][cm_router]["configuration"]["routing_rules"]["tags"]["test_tag"]);
+EXPECT_JSON_EQ(567, clusterset.routerOptions({"extended": 2})["routers"][cm_router]["configuration"]["routing_rules"]["tags"]["test_tag"]);
 
 clusterset.setRoutingOption("tag:test_tag", 567);
-EXPECT_JSON_EQ(567, clusterset.routingOptions()["global"]["tags"]["test_tag"]);
+EXPECT_JSON_EQ(567, clusterset.routerOptions()["configuration"]["routing_rules"]["tags"]["test_tag"]);
 
 clusterset.setRoutingOption("tag:test_tag", null);
 clusterset.setRoutingOption(cm_router, "tag:test_tag", null);
-EXPECT_JSON_EQ(null, clusterset.routingOptions()["routers"][cm_router]["tags"]["test_tag"]);
-EXPECT_JSON_EQ(null, clusterset.routingOptions()["global"]["tags"]["test_tag"]);
+EXPECT_JSON_EQ({}, clusterset.routerOptions({"extended": 2})["routers"][cm_router]["configuration"]["routing_rules"]["tags"]);
+EXPECT_JSON_EQ({}, clusterset.routerOptions()["configuration"]["routing_rules"]["tags"]);
+
+clusterset.setRoutingOption("tag:test_tag", 567);
+clusterset.setRoutingOption(cm_router, "tag:test_tag", 567);
 
 clusterset.setRoutingOption("tags", null);
 clusterset.setRoutingOption(cm_router, "tags", null);
-EXPECT_JSON_EQ(undefined, clusterset.routingOptions()["routers"][cm_router]["tags"]);
-EXPECT_JSON_EQ({}, clusterset.routingOptions()["global"]["tags"]);
+EXPECT_JSON_EQ({}, clusterset.routerOptions({"extended": 2})["routers"][cm_router]["configuration"]["routing_rules"]["tags"]);
+EXPECT_JSON_EQ({}, clusterset.routerOptions()["configuration"]["routing_rules"]["tags"]);
 
 //@<> clusterset.setRoutingOption, all valid values
 function CHECK_SET_ROUTING_OPTION(option, value, expected_value) {
-  orig_options = clusterset.routingOptions();
+  orig_options = clusterset.routerOptions({"extended": 2});
 
-  router_options = clusterset.routingOptions(cm_router);
-  global_options = clusterset.routingOptions();
+  router_options = clusterset.routerOptions({"router": cm_router, "extended": 2});
+  global_options = clusterset.routerOptions({"extended": 2});
 
   clusterset.setRoutingOption(cm_router, option, value);
-  router_options[cm_router][option] = expected_value;
-  global_options["routers"][cm_router][option] = expected_value;
-  EXPECT_JSON_EQ(router_options, clusterset.routingOptions(cm_router), "router check");
-  EXPECT_JSON_EQ(global_options, clusterset.routingOptions(), "router check 2");
+  router_options["routers"][cm_router]["configuration"]["routing_rules"][option] = expected_value;
+  global_options["routers"][cm_router]["configuration"]["routing_rules"][option] = expected_value;
+  EXPECT_JSON_EQ(router_options, clusterset.routerOptions({"router": cm_router, "extended": 2}), "router check");
+  EXPECT_JSON_EQ(global_options, clusterset.routerOptions({"extended": 2}), "router check 2");
 
   clusterset.setRoutingOption(option, value);
-  global_options["global"][option] = expected_value;
-  EXPECT_JSON_EQ(global_options, clusterset.routingOptions(), "global check");
+  global_options["configuration"]["routing_rules"][option] = expected_value;
+  global_options["routers"][cr_router]["configuration"]["routing_rules"][option] = expected_value;
+  global_options["routers"][cr_router2]["configuration"]["routing_rules"][option] = expected_value;
+  global_options["routers"][cr_router3]["configuration"]["routing_rules"][option] = expected_value;
+  EXPECT_JSON_EQ(global_options, clusterset.routerOptions({"extended": 2}), "global check");
 
   // setting option to null should reset to default
   clusterset.setRoutingOption(option, null);
   clusterset.setRoutingOption(cm_router, option, null);
-  EXPECT_JSON_EQ(orig_options, clusterset.routingOptions(), "original check");
+  EXPECT_JSON_EQ(orig_options, clusterset.routerOptions({"extended": 2}), "original check");
 }
 
 CHECK_SET_ROUTING_OPTION("target_cluster", "primary", "primary");
@@ -105,22 +111,32 @@ CHECK_SET_ROUTING_OPTION('read_only_targets', 'all', 'all');
 CHECK_SET_ROUTING_OPTION('read_only_targets', 'read_replicas', 'read_replicas');
 CHECK_SET_ROUTING_OPTION('read_only_targets', 'secondaries', 'secondaries');
 
-//@<> default values filled in when metadata is missing some option (e.g. upgrade)
-var full_options = clusterset.routingOptions();
+//@<> default values are not filled in when metadata is missing some option (e.g. upgrade)
+var full_options = clusterset.routerOptions({"extended": 2});
+
+EXPECT_EQ(full_options["configuration"]["routing_rules"].hasOwnProperty("read_only_targets"),true)
+EXPECT_EQ(full_options["configuration"]["routing_rules"].hasOwnProperty("tags"),true)
 
 var router_options = session.runSql("select router_options from mysql_innodb_cluster_metadata.clustersets").fetchOne()[0]
 session.runSql("update mysql_innodb_cluster_metadata.clustersets set router_options='{}'");
 
-EXPECT_JSON_EQ(full_options, clusterset.routingOptions());
+EXPECT_EQ(clusterset.routerOptions()["configuration"]["routing_rules"].hasOwnProperty("read_only_targets"),false)
+EXPECT_EQ(clusterset.routerOptions()["configuration"]["routing_rules"].hasOwnProperty("tags"),false)
+
+EXPECT_JSON_EQ({}, clusterset.routerOptions()["configuration"]["routing_rules"])
+EXPECT_JSON_EQ({}, clusterset.routerOptions({"extended": 2})["routers"][cm_router]["configuration"]["routing_rules"])
+EXPECT_JSON_EQ({}, clusterset.routerOptions({"extended": 2})["routers"][cr_router]["configuration"]["routing_rules"])
+EXPECT_JSON_EQ({}, clusterset.routerOptions({"extended": 2})["routers"][cr_router2]["configuration"]["routing_rules"])
+EXPECT_JSON_EQ({}, clusterset.routerOptions({"extended": 2})["routers"][cr_router3]["configuration"]["routing_rules"])
 
 session.runSql("update mysql_innodb_cluster_metadata.clustersets set router_options=?", [router_options]);
-EXPECT_JSON_EQ(full_options, clusterset.routingOptions());
+EXPECT_JSON_EQ(full_options, clusterset.routerOptions({"extended": 2}));
 
 //@<> BUG#34458017: setRoutingOption to null resets all options
 // Each option should be reset individually and not all of them
 clusterset.setRoutingOption("stats_updates_frequency", 42);
 clusterset.setRoutingOption(cm_router, "stats_updates_frequency", 44);
-var orig = clusterset.routingOptions();
+var orig = clusterset.routerOptions({"extended": 2});
 
 // WL15601 NFR2: value must be present in the MD
 options = JSON.parse(session.runSql("select router_options from mysql_innodb_cluster_metadata.clustersets").fetchOne()[0]);
@@ -129,8 +145,8 @@ options = JSON.parse(session.runSql("select options from mysql_innodb_cluster_me
 EXPECT_TRUE("stats_updates_frequency" in options);
 
 clusterset.setRoutingOption(cm_router, "stats_updates_frequency", null);
-delete orig["routers"][cm_router]["stats_updates_frequency"];
-EXPECT_JSON_EQ(orig, clusterset.routingOptions());
+orig["routers"][cm_router]["configuration"]["routing_rules"]["stats_updates_frequency"] = 42;
+EXPECT_JSON_EQ(orig, clusterset.routerOptions({"extended": 2}));
 
 // WL15601 NFR2: value must *not* be present in the MD
 options = JSON.parse(session.runSql("select options from mysql_innodb_cluster_metadata.routers").fetchOne()[0]);
@@ -146,15 +162,15 @@ clusterset.setRoutingOption("tags", {"old":"oldvalue"});
 
 clusterset.setRoutingOption("tag:test_tag", 1234);
 clusterset.setRoutingOption("tag:bla", "test");
-EXPECT_JSON_EQ({"old":"oldvalue", "test_tag":1234, "bla": "test"}, clusterset.routingOptions()["global"]["tags"]);
+EXPECT_JSON_EQ({"old":"oldvalue", "test_tag":1234, "bla": "test"}, clusterset.routerOptions()["configuration"]["routing_rules"]["tags"]);
 clusterset.setRoutingOption("tags", {});
-EXPECT_JSON_EQ({}, clusterset.routingOptions()["global"]["tags"]);
+EXPECT_JSON_EQ({}, clusterset.routerOptions()["configuration"]["routing_rules"]["tags"]);
 
 clusterset.setRoutingOption(cm_router, "tags", {"old":"oldvalue"});
 clusterset.setRoutingOption(cm_router, "tags", {"test_tag":1234});
-EXPECT_JSON_EQ({"test_tag":1234}, clusterset.routingOptions()["routers"][cm_router]["tags"]);
+EXPECT_JSON_EQ({"test_tag":1234}, clusterset.routerOptions({"extended": 2})["routers"][cm_router]["configuration"]["routing_rules"]["tags"]);
 clusterset.setRoutingOption(cm_router, "tags", {});
-EXPECT_JSON_EQ({}, clusterset.routingOptions()["routers"][cm_router]["tags"]);
+EXPECT_JSON_EQ({}, clusterset.routerOptions({"extended": 2})["routers"][cm_router]["configuration"]["routing_rules"]["tags"]);
 
 //@<> clusterset.setRoutingOption for a router, invalid values
 EXPECT_THROWS(function(){ clusterset.setRoutingOption(cm_router, "target_cluster", 'any_not_supported_value'); },
@@ -217,12 +233,12 @@ EXPECT_THROWS(function(){ clusterset.setRoutingOption("::system", 'read_only_tar
 
 //@<> check types of clusterset router option values
 // Bug#34604612
-options = clusterset.routingOptions();
-EXPECT_EQ("string", typeof options["global"]["target_cluster"]);
-EXPECT_EQ("string", typeof options["global"]["invalidated_cluster_policy"]);
-EXPECT_EQ("number", typeof options["global"]["stats_updates_frequency"]);
-EXPECT_EQ("boolean", typeof options["global"]["use_replica_primary_as_rw"]);
-EXPECT_EQ("string", typeof options["global"]["read_only_targets"]);
+options = clusterset.routerOptions();
+EXPECT_EQ("string", typeof options["configuration"]["routing_rules"]["target_cluster"]);
+EXPECT_EQ("string", typeof options["configuration"]["routing_rules"]["invalidated_cluster_policy"]);
+EXPECT_EQ("number", typeof options["configuration"]["routing_rules"]["stats_updates_frequency"]);
+EXPECT_EQ("boolean", typeof options["configuration"]["routing_rules"]["use_replica_primary_as_rw"]);
+EXPECT_EQ("string", typeof options["configuration"]["routing_rules"]["read_only_targets"]);
 
 options = JSON.parse(session.runSql("select router_options from mysql_innodb_cluster_metadata.clustersets").fetchOne()[0]);
 EXPECT_EQ("string", typeof options["target_cluster"]);
@@ -324,7 +340,7 @@ EXPECT_NO_THROWS(function(){ clusterset.setRoutingOption("tag:two", 2); });
 session.runSql("UPDATE mysql_innodb_cluster_metadata.clustersets SET router_options = '{}'");
 EXPECT_NO_THROWS(function(){ clusterset.setRoutingOption("tags", {"one":1, "two":"2"}); });
 
-//@<> cluster.routingOptions(), for global options that also exist in clusterset.routingOptions(), should show the values (except tags) stored on the ClusterSet
+//@<> cluster.routerOptions(), for global options that also exist in clusterset.routerOptions(), should show the values (except tags) stored on the ClusterSet
 shell.connect(__sandbox_uri3);
 reset_instance(session);
 shell.connect(__sandbox_uri2);
@@ -337,28 +353,28 @@ EXPECT_NO_THROWS(function() {cluster = dba.createCluster("cluster", {gtidSetIsCo
 EXPECT_NO_THROWS(function(){ cluster.setRoutingOption("tag:five", 5); });
 EXPECT_NO_THROWS(function(){ cluster.setRoutingOption("stats_updates_frequency", 52); });
 
-options = cluster.routingOptions();
-EXPECT_EQ("number", typeof options["global"]["tags"]["five"]);
-EXPECT_EQ(5, options["global"]["tags"]["five"]);
-EXPECT_EQ("number", typeof options["global"]["stats_updates_frequency"]);
-EXPECT_EQ(52, options["global"]["stats_updates_frequency"]);
+options = cluster.routerOptions();
+EXPECT_EQ("number", typeof options["configuration"]["routing_rules"]["tags"]["five"]);
+EXPECT_EQ(5, options["configuration"]["routing_rules"]["tags"]["five"]);
+EXPECT_EQ("number", typeof options["configuration"]["routing_rules"]["stats_updates_frequency"]);
+EXPECT_EQ(52, options["configuration"]["routing_rules"]["stats_updates_frequency"]);
 
 EXPECT_NO_THROWS(function() {clusterset = cluster.createClusterSet("clusterset"); });
 EXPECT_NO_THROWS(function(){ clusterset.setRoutingOption("tag:six", 6); });
 EXPECT_NO_THROWS(function(){ clusterset.setRoutingOption("stats_updates_frequency", 66); });
 
-options = cluster.routingOptions();
-EXPECT_EQ("number", typeof options["global"]["tags"]["five"]);
-EXPECT_EQ(5, options["global"]["tags"]["five"]);
-EXPECT_EQ("number", typeof options["global"]["stats_updates_frequency"]);
-EXPECT_EQ(66, options["global"]["stats_updates_frequency"]);
+options = clusterset.routerOptions();
+EXPECT_EQ("number", typeof options["configuration"]["routing_rules"]["tags"]["six"]);
+EXPECT_EQ(6, options["configuration"]["routing_rules"]["tags"]["six"]);
+EXPECT_EQ("number", typeof options["configuration"]["routing_rules"]["stats_updates_frequency"]);
+EXPECT_EQ(66, options["configuration"]["routing_rules"]["stats_updates_frequency"]);
 
 //@<> check if "stats_updates_frequency", in ClusterSets, defaults to 0
 EXPECT_NO_THROWS(function(){ clusterset.setRoutingOption("stats_updates_frequency", 48); });
 
-options = clusterset.routingOptions();
-EXPECT_EQ("number", typeof options["global"]["stats_updates_frequency"]);
-EXPECT_EQ(48, options["global"]["stats_updates_frequency"]);
+options = clusterset.routerOptions();
+EXPECT_EQ("number", typeof options["configuration"]["routing_rules"]["stats_updates_frequency"]);
+EXPECT_EQ(48, options["configuration"]["routing_rules"]["stats_updates_frequency"]);
 
 options = JSON.parse(session.runSql("select router_options from mysql_innodb_cluster_metadata.clustersets").fetchOne()[0]);
 EXPECT_TRUE("stats_updates_frequency" in options);
@@ -366,9 +382,8 @@ EXPECT_EQ(48, options["stats_updates_frequency"]);
 
 EXPECT_NO_THROWS(function(){ clusterset.setRoutingOption("stats_updates_frequency", null); });
 
-options = clusterset.routingOptions();
-EXPECT_EQ("object", typeof options["global"]["stats_updates_frequency"]);
-EXPECT_EQ(null, options["global"]["stats_updates_frequency"]);
+options = clusterset.routerOptions();
+EXPECT_EQ(false, options["configuration"]["routing_rules"].hasOwnProperty("stats_updates_frequency"));
 
 options = JSON.parse(session.runSql("select router_options from mysql_innodb_cluster_metadata.clustersets").fetchOne()[0]);
 EXPECT_FALSE("stats_updates_frequency" in options);
@@ -377,9 +392,9 @@ set_metadata_version(2, 1, 0);
 
 EXPECT_NO_THROWS(function(){ clusterset.setRoutingOption("stats_updates_frequency", 47); });
 
-options = clusterset.routingOptions();
-EXPECT_EQ("number", typeof options["global"]["stats_updates_frequency"]);
-EXPECT_EQ(47, options["global"]["stats_updates_frequency"]);
+options = clusterset.routerOptions();
+EXPECT_EQ("number", typeof options["configuration"]["routing_rules"]["stats_updates_frequency"]);
+EXPECT_EQ(47, options["configuration"]["routing_rules"]["stats_updates_frequency"]);
 
 options = JSON.parse(session.runSql("select router_options from mysql_innodb_cluster_metadata.clustersets").fetchOne()[0]);
 EXPECT_TRUE("stats_updates_frequency" in options);
@@ -387,9 +402,9 @@ EXPECT_EQ(47, options["stats_updates_frequency"]);
 
 EXPECT_NO_THROWS(function(){ clusterset.setRoutingOption("stats_updates_frequency", null); });
 
-options = clusterset.routingOptions();
-EXPECT_EQ("number", typeof options["global"]["stats_updates_frequency"]);
-EXPECT_EQ(0, options["global"]["stats_updates_frequency"]);
+options = clusterset.routerOptions();
+EXPECT_EQ("number", typeof options["configuration"]["routing_rules"]["stats_updates_frequency"]);
+EXPECT_EQ(0, options["configuration"]["routing_rules"]["stats_updates_frequency"]);
 
 options = JSON.parse(session.runSql("select router_options from mysql_innodb_cluster_metadata.clustersets").fetchOne()[0]);
 EXPECT_TRUE("stats_updates_frequency" in options);
diff --git a/unittest/scripts/auto/js_adminapi_clusterset/scripts/repl_options_set.js b/unittest/scripts/auto/js_adminapi_clusterset/scripts/repl_options_set.js
index 4582519aa4..f9898826ef 100644
--- a/unittest/scripts/auto/js_adminapi_clusterset/scripts/repl_options_set.js
+++ b/unittest/scripts/auto/js_adminapi_clusterset/scripts/repl_options_set.js
@@ -156,11 +156,27 @@ EXPECT_THROWS(function() {
 // fails because of network related options
 WIPE_SHELL_LOG();
 
-begin_dba_log_sql();
+shell.options["logSql"] = "on";
 EXPECT_THROWS(function() {
  cset.createReplicaCluster(__sandbox_uri2, "c2", {clusterSetReplicationConnectRetry: 10, clusterSetReplicationRetryCount: 11, clusterSetReplicationHeartbeatPeriod: 33.3, clusterSetReplicationCompressionAlgorithms: "zlib", clusterSetReplicationZstdCompressionLevel: "4", clusterSetReplicationBind: "foo", clusterSetReplicationNetworkNamespace: "bar"});
 }, (__version_num >= 80400) ? "Error found in replication receiver thread" : "Replication thread not in expected state");
-end_dba_log_sql();
+shell.options["logSql"] = "off";
+
+EXPECT_SHELL_LOG_NOT_CONTAINS("SOURCE_CONNECT_RETRY=10");
+EXPECT_SHELL_LOG_NOT_CONTAINS("SOURCE_RETRY_COUNT=11");
+EXPECT_SHELL_LOG_NOT_CONTAINS("SOURCE_HEARTBEAT_PERIOD=33.3");
+EXPECT_SHELL_LOG_NOT_CONTAINS("SOURCE_COMPRESSION_ALGORITHMS='zlib'");
+EXPECT_SHELL_LOG_NOT_CONTAINS("SOURCE_ZSTD_COMPRESSION_LEVEL=4");
+EXPECT_SHELL_LOG_NOT_CONTAINS("SOURCE_BIND='foo'");
+EXPECT_SHELL_LOG_NOT_CONTAINS("NETWORK_NAMESPACE='bar'");
+
+WIPE_SHELL_LOG();
+
+shell.options["logSql"] = "unfiltered";
+EXPECT_THROWS(function() {
+    cset.createReplicaCluster(__sandbox_uri2, "c2", {clusterSetReplicationConnectRetry: 10, clusterSetReplicationRetryCount: 11, clusterSetReplicationHeartbeatPeriod: 33.3, clusterSetReplicationCompressionAlgorithms: "zlib", clusterSetReplicationZstdCompressionLevel: "4", clusterSetReplicationBind: "foo", clusterSetReplicationNetworkNamespace: "bar"});
+}, (__version_num >= 80400) ? "Error found in replication receiver thread" : "Replication thread not in expected state");
+shell.options["logSql"] = "off";
 
 EXPECT_SHELL_LOG_CONTAINS("SOURCE_CONNECT_RETRY=10");
 EXPECT_SHELL_LOG_CONTAINS("SOURCE_RETRY_COUNT=11");
@@ -305,9 +321,9 @@ check_repl_option_metadata_only(session, "c2", session2, "opt_replNetworkNamespa
 
 WIPE_SHELL_LOG();
 
-begin_dba_log_sql();
+\option logSql = on
 EXPECT_NO_THROWS(function() { cset.rejoinCluster("c2"); });
-end_dba_log_sql();
+\option --unset logSql
 
 // because we add a NULL option, the channel should have been reset
 EXPECT_SHELL_LOG_CONTAINS("RESET REPLICA ALL FOR CHANNEL 'clusterset_replication'");
@@ -330,9 +346,9 @@ EXPECT_NO_THROWS(function() { c2.setOption("clusterSetReplicationCompressionAlgo
 
 WIPE_SHELL_LOG();
 
-begin_dba_log_sql();
+\option logSql = on
 EXPECT_NO_THROWS(function() { cset.rejoinCluster("c2"); });
-end_dba_log_sql();
+\option --unset logSql
 
 EXPECT_SHELL_LOG_NOT_CONTAINS("RESET REPLICA ALL FOR CHANNEL 'clusterset_replication'");
 
diff --git a/unittest/scripts/auto/js_adminapi_clusterset/scripts/routing_guideline.js b/unittest/scripts/auto/js_adminapi_clusterset/scripts/routing_guideline.js
index 3ae6a7e689..5dabb214dd 100644
--- a/unittest/scripts/auto/js_adminapi_clusterset/scripts/routing_guideline.js
+++ b/unittest/scripts/auto/js_adminapi_clusterset/scripts/routing_guideline.js
@@ -262,6 +262,18 @@ cluster = dba.getCluster();
 var router_options = cluster.routerOptions();
 EXPECT_EQ(undefined, router_options["configuration"]["routing_rules"]["guideline"]);
 
+//@<> .show() must require the 'router' option when a destination includes $.router.* vars
+
+// Add a new destination with a $.router var
+rg.addDestination("RouterVar", "$.server.memberRole = SECONDARY AND $.server.clusterName = $.router.localCluster")
+
+// Check the error
+EXPECT_THROWS(function(){ rg.show(); }, "Option 'router' not set: The Routing Guideline contains destinations configured to use router-specific variables.");
+EXPECT_OUTPUT_CONTAINS("ERROR: The Routing Guideline contains destinations configured to use router-specific variables (e.g., '$.router.localCluster'), but the 'router' option is not set. This may cause potential mismatches with the live router behavior. Please set the 'router' option to specify the Router that should be used for evaluation.");
+
+// Confirm no error when the option is given
+EXPECT_NO_THROWS(function() { rg.show({router: router1}); });
+
 //@<> Cleanup
 scene.destroy();
 testutil.destroySandbox(__mysql_sandbox_port2);
diff --git a/unittest/scripts/auto/js_adminapi_clusterset/scripts/set_primary.js b/unittest/scripts/auto/js_adminapi_clusterset/scripts/set_primary.js
index cae3c6c0d8..d80375c646 100644
--- a/unittest/scripts/auto/js_adminapi_clusterset/scripts/set_primary.js
+++ b/unittest/scripts/auto/js_adminapi_clusterset/scripts/set_primary.js
@@ -71,7 +71,7 @@ EXPECT_OUTPUT_NOT_CONTAINS("ERROR");
 EXPECT_THROWS(function(){cs.setPrimaryCluster("cluster3");}, "The cluster with the name 'cluster3' does not exist.");
 
 //@<> Switch primary 1/1 (dryRun)
-shell.options["dba.logSql"] = 2;
+shell.options["logSql"] = "unfiltered";
 
 cs.setPrimaryCluster("cluster2", {dryRun:1});
 
@@ -86,7 +86,7 @@ EXPECT_OUTPUT_CONTAINS("* Acquiring locks in ClusterSet instances");
 
 EXPECT_SHELL_LOG_NOT_CONTAINS("FLUSH TABLES WITH READ LOCK");
 
-shell.options["dba.logSql"] = 0;
+shell.options["logSql"] = "off";
 
 //@<> Switch primary 1/1
 cs.setPrimaryCluster("cluster2");
diff --git a/unittest/scripts/auto/js_adminapi_clusterset/scripts/set_primary_revert.js b/unittest/scripts/auto/js_adminapi_clusterset/scripts/set_primary_revert.js
index ecbbb3cff8..9e64ecb9dd 100644
--- a/unittest/scripts/auto/js_adminapi_clusterset/scripts/set_primary_revert.js
+++ b/unittest/scripts/auto/js_adminapi_clusterset/scripts/set_primary_revert.js
@@ -100,7 +100,7 @@ session5.runSql("set sql_log_bin=1");
 session5.runSql("set global super_read_only=1");
 
 shell.options.verbose=3;
-shell.options["dba.logSql"]=1;
+shell.options["logSql"]="on";
 
 EXPECT_THROWS(function(){cs.setPrimaryCluster("cluster2");}, "Could not update replication source of "+hostname+":"+__mysql_sandbox_port5);
 
diff --git a/unittest/scripts/auto/js_adminapi_clusterset/scripts/simple_hostname_ci.js b/unittest/scripts/auto/js_adminapi_clusterset/scripts/simple_hostname_ci.js
index 7c857e1af6..ce05f8b699 100644
--- a/unittest/scripts/auto/js_adminapi_clusterset/scripts/simple_hostname_ci.js
+++ b/unittest/scripts/auto/js_adminapi_clusterset/scripts/simple_hostname_ci.js
@@ -24,9 +24,11 @@ shell.connect(__sandbox_uri1);
 var clusterset_id = session.runSql("SELECT clusterset_id FROM mysql_innodb_cluster_metadata.clustersets").fetchOne()[0];
 session.runSql("INSERT mysql_innodb_cluster_metadata.routers VALUES (1, 'system', 'mysqlrouter', 'RoutERHosT1', '8.0.27', '2021-01-01 11:22:33', '{\"bootstrapTargetType\": \"clusterset\", \"ROEndpoint\": \"6481\", \"RWEndpoint\": \"6480\", \"ROXEndpoint\": \"6483\", \"RWXEndpoint\": \"6482\"}', NULL, NULL, ?)", [clusterset_id]);
 
-EXPECT_EQ(cs.routingOptions()["routers"]["RoutERHosT1::system"].hasOwnProperty("target_cluster"), false);
-EXPECT_NO_THROWS(function() { cs.setRoutingOption("routerhost1::system", "target_cluster", "primary"); });
-EXPECT_EQ(cs.routingOptions()["routers"]["RoutERHosT1::system"].hasOwnProperty("target_cluster"), true);
+global_options = cs.routerOptions()["configuration"]["routing_rules"]
+EXPECT_EQ(global_options["target_cluster"], "primary")
+EXPECT_EQ(cs.routerOptions({"extended": 2})["routers"]["RoutERHosT1::system"]["configuration"]["routing_rules"]["target_cluster"], global_options["target_cluster"]);
+EXPECT_NO_THROWS(function() { cs.setRoutingOption("routerhost1::system", "target_cluster", "cluster"); });
+EXPECT_NE(cs.routerOptions({"extended": 2})["routers"]["RoutERHosT1::system"]["configuration"]["routing_rules"]["target_cluster"], global_options["target_cluster"]);
 
 //@<> Cleanup
 testutil.destroySandbox(__mysql_sandbox_port1);
diff --git a/unittest/scripts/auto/js_adminapi_clusterset/scripts/simple_plain_cs_mysql_comm_stack_norecord.js b/unittest/scripts/auto/js_adminapi_clusterset/scripts/simple_plain_cs_mysql_comm_stack_norecord.js
index 231a6cd3a9..c15ef9949a 100644
--- a/unittest/scripts/auto/js_adminapi_clusterset/scripts/simple_plain_cs_mysql_comm_stack_norecord.js
+++ b/unittest/scripts/auto/js_adminapi_clusterset/scripts/simple_plain_cs_mysql_comm_stack_norecord.js
@@ -285,7 +285,7 @@ CHECK_PRIMARY_CLUSTER([__sandbox_uri1, __sandbox_uri2, __sandbox_uri3], cluster)
 // delete sb3 from the metadata so that rescan picks it up
 session.runSql("DELETE FROM mysql_innodb_cluster_metadata.instances WHERE instance_name LIKE ?", ["%:"+__mysql_sandbox_port3]);
 
-EXPECT_NO_THROWS(function() { cluster.rescan({addInstances: "auto"}); });
+EXPECT_NO_THROWS(function() { cluster.rescan({addUnmanaged: "true"}); });
 testutil.waitMemberState(__mysql_sandbox_port3, "ONLINE");
 CHECK_PRIMARY_CLUSTER([__sandbox_uri1, __sandbox_uri2, __sandbox_uri3], cluster);
 
@@ -329,7 +329,7 @@ CHECK_REPLICA_CLUSTER([__sandbox_uri4, __sandbox_uri3], cluster, replicacluster)
 shell.connect(__sandbox_uri1);
 session.runSql("DELETE FROM mysql_innodb_cluster_metadata.instances WHERE instance_name LIKE ?", ["%:"+__mysql_sandbox_port3]);
 
-EXPECT_NO_THROWS(function() { replicacluster.rescan({addInstances: "auto"}); });
+EXPECT_NO_THROWS(function() { replicacluster.rescan({addUnmanaged: "true"}); });
 shell.connect(__sandbox_uri4);
 testutil.waitMemberState(__mysql_sandbox_port3, "ONLINE");
 CHECK_REPLICA_CLUSTER([__sandbox_uri4, __sandbox_uri3], cluster, replicacluster);
diff --git a/unittest/scripts/auto/js_adminapi_clusterset/scripts/simple_plain_cs_norecord.js b/unittest/scripts/auto/js_adminapi_clusterset/scripts/simple_plain_cs_norecord.js
index f75c3d0871..7af3f76257 100644
--- a/unittest/scripts/auto/js_adminapi_clusterset/scripts/simple_plain_cs_norecord.js
+++ b/unittest/scripts/auto/js_adminapi_clusterset/scripts/simple_plain_cs_norecord.js
@@ -304,7 +304,7 @@ CHECK_PRIMARY_CLUSTER([__sandbox_uri1, __sandbox_uri2, __sandbox_uri3], cluster)
 // delete sb3 from the metadata so that rescan picks it up
 session.runSql("DELETE FROM mysql_innodb_cluster_metadata.instances WHERE instance_name LIKE ?", ["%:"+__mysql_sandbox_port3]);
 
-EXPECT_NO_THROWS(function() { cluster.rescan({addInstances: "auto"}); });
+EXPECT_NO_THROWS(function() { cluster.rescan({addUnmanaged: "true"}); });
 testutil.waitMemberState(__mysql_sandbox_port3, "ONLINE");
 CHECK_PRIMARY_CLUSTER([__sandbox_uri1, __sandbox_uri2, __sandbox_uri3], cluster);
 
@@ -345,7 +345,7 @@ CHECK_REPLICA_CLUSTER([__sandbox_uri4, __sandbox_uri3], cluster, replicacluster,
 connect_no_timeout(__sandbox_uri1);
 session.runSql("DELETE FROM mysql_innodb_cluster_metadata.instances WHERE instance_name LIKE ?", ["%:"+__mysql_sandbox_port3]);
 
-EXPECT_NO_THROWS(function() { replicacluster.rescan({addInstances: "auto"}); });
+EXPECT_NO_THROWS(function() { replicacluster.rescan({addUnmanaged: "true"}); });
 connect_no_timeout(__sandbox_uri4);
 testutil.waitMemberState(__mysql_sandbox_port3, "ONLINE");
 CHECK_REPLICA_CLUSTER([__sandbox_uri4, __sandbox_uri3], cluster, replicacluster, undefined, __secure_password);
diff --git a/unittest/scripts/auto/js_adminapi_clusterset/validation/cs_help.js b/unittest/scripts/auto/js_adminapi_clusterset/validation/cs_help.js
index 68cbfa6962..7d3445fb27 100644
--- a/unittest/scripts/auto/js_adminapi_clusterset/validation/cs_help.js
+++ b/unittest/scripts/auto/js_adminapi_clusterset/validation/cs_help.js
@@ -76,13 +76,6 @@ FUNCTIONS
       routingGuidelines()
             Lists the Routing Guidelines defined for the ClusterSet.
 
-      routingOptions([router])
-            Lists the ClusterSet Routers configuration options.
-
-            ATTENTION: This function is deprecated and will be removed in a
-                       future release of MySQL Shell. Use
-                       ClusterSet.routerOptions() instead.
-
       setOption(option, value)
             Changes the value of an option for the whole ClusterSet.
 
@@ -923,29 +916,6 @@ DESCRIPTION
       - targetCluster: Target Cluster for Router routing operations.
       - version: Version.
 
-//@<OUT> routingOptions
-NAME
-      routingOptions - Lists the ClusterSet Routers configuration options.
-
-SYNTAX
-      <ClusterSet>.routingOptions([router])
-
-WHERE
-      router: Identifier of the router instance to query for the options.
-
-RETURNS
-      A JSON object describing the configuration options of all router
-      instances of the ClusterSet and its global options or just the given
-      Router.
-
-DESCRIPTION
-      ATTENTION: This function is deprecated and will be removed in a future
-                 release of MySQL Shell. Use ClusterSet.routerOptions()
-                 instead.
-
-      This function lists the Router configuration options of all Routers of
-      the ClusterSet or the target Router.
-
 //@<OUT> routerOptions
 NAME
       routerOptions - Lists the configuration options of the ClusterSet's
@@ -1347,5 +1317,11 @@ DESCRIPTION
 
       - force (boolean): Allows overwriting an existing Routing Guideline with
         the same name. Disabled by default.
+      - rename (string): Allows importing a guideline under a new name; for
+        example, if a guideline with the same name already exists in the
+        ClusterSet.
+
+      The options 'force' and 'rename' are mutually exclusive. Only one can be
+      used at a time.
 
       For more information on Routing Guidelines, see \? RoutingGuideline.
diff --git a/unittest/scripts/auto/js_adminapi_clusterset/validation/cs_router_management.js b/unittest/scripts/auto/js_adminapi_clusterset/validation/cs_router_management.js
index fc4f8660df..d706bd033c 100644
--- a/unittest/scripts/auto/js_adminapi_clusterset/validation/cs_router_management.js
+++ b/unittest/scripts/auto/js_adminapi_clusterset/validation/cs_router_management.js
@@ -1,23 +1,32 @@
-//@<OUT> clusterset.routingOptions() with all defaults
+//@<OUT> clusterset.routerOptions() with all defaults
 {
-    "domainName": "clusterset",
-    "global": {
-        "guideline": null,
-        "invalidated_cluster_policy": "drop_all",
-        "read_only_targets": "secondaries",
-        "stats_updates_frequency": null,
-        "tags": {},
-        "target_cluster": "primary",
-        "use_replica_primary_as_rw": false
+    "configuration": {
+        "routing_rules": {
+            "invalidated_cluster_policy": "drop_all",
+            "read_only_targets": "secondaries",
+            "tags": {},
+            "target_cluster": "primary",
+            "use_replica_primary_as_rw": false
+        }
     },
+    "domainName": "clusterset",
     "routers": {
-        "routerhost1::system": {},
-        "routerhost2::": {},
-        "routerhost2::another": {},
-        "routerhost2::system": {}
+        "routerhost1::system": {
+            "configuration": {}
+        },
+        "routerhost2::": {
+            "configuration": {}
+        },
+        "routerhost2::another": {
+            "configuration": {}
+        },
+        "routerhost2::system": {
+            "configuration": {}
+        }
     }
 }
 
+
 //@<OUT> clusterset.listRouters (CLI)
 {
     "domainName": "clusterset",
diff --git a/unittest/scripts/auto/js_credential/scripts/shell_store_credential_bugfixes_norecord_interactive.js b/unittest/scripts/auto/js_credential/scripts/shell_store_credential_bugfixes_norecord_interactive.js
index 910c1af9ab..b745b09b5d 100644
--- a/unittest/scripts/auto/js_credential/scripts/shell_store_credential_bugfixes_norecord_interactive.js
+++ b/unittest/scripts/auto/js_credential/scripts/shell_store_credential_bugfixes_norecord_interactive.js
@@ -1,5 +1,7 @@
+//@ {shell.listCredentialHelpers().includes("login-path")}
+
 //@ Initialization
-shell.options["credentialStore.helper"] = "plaintext";
+shell.options["credentialStore.helper"] = "login-path";
 
 // BUG#28597766 NEWLINES PRODUCE ERROR IN PASSWORD HELPER
 
diff --git a/unittest/scripts/auto/js_credential/validation/shell_store_credential_bugfixes_norecord_interactive.js b/unittest/scripts/auto/js_credential/validation/shell_store_credential_bugfixes_norecord_interactive.js
index c1add27345..c5396bde62 100644
--- a/unittest/scripts/auto/js_credential/validation/shell_store_credential_bugfixes_norecord_interactive.js
+++ b/unittest/scripts/auto/js_credential/validation/shell_store_credential_bugfixes_norecord_interactive.js
@@ -1,5 +1,5 @@
 //@ Initialization
-|plaintext|
+|login-path|
 
 //@ BUG#28597766 store a password - line feed character
-||Failed to save the credential: Unable to store password with a newline character (RuntimeError)
+||Failed to save the credential: Secret contains a disallowed character (RuntimeError)
diff --git a/unittest/scripts/auto/js_devapi/validation/mysqlx_help_norecord.js b/unittest/scripts/auto/js_devapi/validation/mysqlx_help_norecord.js
index 0652a36a14..b19a69c1c0 100644
--- a/unittest/scripts/auto/js_devapi/validation/mysqlx_help_norecord.js
+++ b/unittest/scripts/auto/js_devapi/validation/mysqlx_help_norecord.js
@@ -784,6 +784,9 @@ FUNCTIONS
       dropSchema(name)
             Drops the schema with the specified name.
 
+      getClientData(key)
+            Returns value associated with the session for the given key.
+
       getConnectionId()
             Retrieves the connection id for the current session.
 
@@ -800,6 +803,9 @@ FUNCTIONS
       getSchemas()
             Retrieves the Schemas available on the session.
 
+      getSqlMode()
+            Retrieves the SQL_MODE for the current session.
+
       getSshUri()
             Retrieves the SSH URI for the current session.
 
@@ -829,6 +835,9 @@ FUNCTIONS
       runSql(query[, args])
             Executes a query and returns the corresponding SqlResult object.
 
+      setClientData(key, value)
+            Associates a value with the session for the given key.
+
       setCurrentSchema(name)
             Sets the current schema for this session, and returns the schema
             object for it.
diff --git a/unittest/scripts/auto/js_devapi/validation/mysqlx_session_help.js b/unittest/scripts/auto/js_devapi/validation/mysqlx_session_help.js
index 1e2bde4771..1d7bb7c379 100644
--- a/unittest/scripts/auto/js_devapi/validation/mysqlx_session_help.js
+++ b/unittest/scripts/auto/js_devapi/validation/mysqlx_session_help.js
@@ -48,6 +48,9 @@ FUNCTIONS
       dropSchema(name)
             Drops the schema with the specified name.
 
+      getClientData(key)
+            Returns value associated with the session for the given key.
+
       getConnectionId()
             Retrieves the connection id for the current session.
 
@@ -64,6 +67,9 @@ FUNCTIONS
       getSchemas()
             Retrieves the Schemas available on the session.
 
+      getSqlMode()
+            Retrieves the SQL_MODE for the current session.
+
       getSshUri()
             Retrieves the SSH URI for the current session.
 
@@ -93,6 +99,9 @@ FUNCTIONS
       runSql(query[, args])
             Executes a query and returns the corresponding SqlResult object.
 
+      setClientData(key, value)
+            Associates a value with the session for the given key.
+
       setCurrentSchema(name)
             Sets the current schema for this session, and returns the schema
             object for it.
diff --git a/unittest/scripts/auto/js_mixed_versions/scripts/dba_sandbox_custom_path_norecord.js b/unittest/scripts/auto/js_mixed_versions/scripts/dba_sandbox_custom_path_norecord.js
new file mode 100644
index 0000000000..cae22413d5
--- /dev/null
+++ b/unittest/scripts/auto/js_mixed_versions/scripts/dba_sandbox_custom_path_norecord.js
@@ -0,0 +1,33 @@
+//@ {DEF(MYSQLD_SECONDARY_SERVER_A)}
+
+//@<> Testing Sandbox Deployment
+let paths = [MYSQLD_SECONDARY_SERVER_A.path]
+paths.push(os.path.dirname(paths[0]));
+
+if (paths[1].endsWith('bin')) {
+    paths.push(os.path.dirname(paths[1]));
+}
+
+for(index in paths) {
+    let path = paths[index];
+
+    print(`Testing: ${path}`)
+
+    dba.deploySandboxInstance(__mysql_sandbox_port1,  { password: 'root', mysqldPath: path });
+    EXPECT_NO_THROWS(function() {shell.connect(__sandbox_uri1)})
+
+    //@<> Stop
+    EXPECT_NO_THROWS(function() { dba.stopSandboxInstance(__mysql_sandbox_port1, {password: 'root'}) });
+
+    //@<> Start
+    EXPECT_NO_THROWS(function() { dba.startSandboxInstance(__mysql_sandbox_port1) });
+    EXPECT_NO_THROWS(function() {shell.connect(__sandbox_uri1)})
+    EXPECT_NO_THROWS(function() {shell.disconnect()})
+
+    //@<> Kill
+    EXPECT_NO_THROWS(function() { dba.killSandboxInstance(__mysql_sandbox_port1) });
+
+    //@<> Delete
+    EXPECT_NO_THROWS(function() { dba.deleteSandboxInstance(__mysql_sandbox_port1) });
+}
+
diff --git a/unittest/scripts/auto/js_mixed_versions/scripts/simple_plain_cs_mixed_versions_norecord.js b/unittest/scripts/auto/js_mixed_versions/scripts/simple_plain_cs_mixed_versions_norecord.js
index e423075b09..7318416d58 100644
--- a/unittest/scripts/auto/js_mixed_versions/scripts/simple_plain_cs_mixed_versions_norecord.js
+++ b/unittest/scripts/auto/js_mixed_versions/scripts/simple_plain_cs_mixed_versions_norecord.js
@@ -306,7 +306,7 @@ CHECK_PRIMARY_CLUSTER([__sandbox_uri1, __sandbox_uri2, __sandbox_uri3], cluster)
 // delete sb3 from the metadata so that rescan picks it up
 session.runSql("DELETE FROM mysql_innodb_cluster_metadata.instances WHERE instance_name LIKE ?", ["%:"+__mysql_sandbox_port3]);
 
-EXPECT_NO_THROWS(function() { cluster.rescan({addInstances: "auto"}); });
+EXPECT_NO_THROWS(function() { cluster.rescan({addUnmanaged: "true"}); });
 testutil.waitMemberState(__mysql_sandbox_port3, "ONLINE");
 CHECK_PRIMARY_CLUSTER([__sandbox_uri1, __sandbox_uri2, __sandbox_uri3], cluster);
 
@@ -347,7 +347,7 @@ CHECK_REPLICA_CLUSTER([__sandbox_uri4, __sandbox_uri3], cluster, replicacluster,
 shell.connect(__sandbox_uri1);
 session.runSql("DELETE FROM mysql_innodb_cluster_metadata.instances WHERE instance_name LIKE ?", ["%:"+__mysql_sandbox_port3]);
 
-EXPECT_NO_THROWS(function() { replicacluster.rescan({addInstances: "auto"}); });
+EXPECT_NO_THROWS(function() { replicacluster.rescan({addUnmanaged: "true"}); });
 shell.connect(__sandbox_uri4);
 testutil.waitMemberState(__mysql_sandbox_port3, "ONLINE");
 CHECK_REPLICA_CLUSTER([__sandbox_uri4, __sandbox_uri3], cluster, replicacluster, undefined, __secure_password);
diff --git a/unittest/scripts/auto/js_pager/validation/WL10775_TS12_1_norecord_interactive.js b/unittest/scripts/auto/js_pager/validation/WL10775_TS12_1_norecord_interactive.js
index 812d120ba9..0e8d199512 100644
--- a/unittest/scripts/auto/js_pager/validation/WL10775_TS12_1_norecord_interactive.js
+++ b/unittest/scripts/auto/js_pager/validation/WL10775_TS12_1_norecord_interactive.js
@@ -48,6 +48,9 @@ FUNCTIONS
       splitScript(script)
             Split a SQL script into individual statements.
 
+      tokenizeStatement(statement)
+            Lexes a MySQL statement into a list of tokens.
+
       unquoteIdentifier(s)
             Unquote a MySQL identifier.
 
@@ -111,6 +114,9 @@ FUNCTIONS
       splitScript(script)
             Split a SQL script into individual statements.
 
+      tokenizeStatement(statement)
+            Lexes a MySQL statement into a list of tokens.
+
       unquoteIdentifier(s)
             Unquote a MySQL identifier.
 
diff --git a/unittest/scripts/auto/js_pager/validation/WL10775_TS2_1_norecord_interactive.js b/unittest/scripts/auto/js_pager/validation/WL10775_TS2_1_norecord_interactive.js
index 7afb6c0a67..a4e6ed5f6d 100644
--- a/unittest/scripts/auto/js_pager/validation/WL10775_TS2_1_norecord_interactive.js
+++ b/unittest/scripts/auto/js_pager/validation/WL10775_TS2_1_norecord_interactive.js
@@ -53,6 +53,9 @@ FUNCTIONS
       splitScript(script)
             Split a SQL script into individual statements.
 
+      tokenizeStatement(statement)
+            Lexes a MySQL statement into a list of tokens.
+
       unquoteIdentifier(s)
             Unquote a MySQL identifier.
 
@@ -95,9 +98,15 @@ FUNCTIONS
             Commits all the operations executed after a call to
             startTransaction().
 
+      getClientData(key)
+            Returns value associated with the session for the given key.
+
       getConnectionId()
             Retrieves the connection id for the current session.
 
+      getSqlMode()
+            Retrieves the SQL_MODE for the current session.
+
       getSshUri()
             Retrieves the SSH URI for the current session.
 
@@ -118,6 +127,9 @@ FUNCTIONS
             Executes a query and returns the corresponding ClassicResult
             object.
 
+      setClientData(key, value)
+            Associates a value with the session for the given key.
+
       setQueryAttributes()
             Defines query attributes that apply to the next statement sent to
             the server for execution.
diff --git a/unittest/scripts/auto/js_shell/scripts/authentication_kerberos_norecord.js b/unittest/scripts/auto/js_shell/scripts/authentication_kerberos_norecord.js
index ab7c23300b..76252e0e5e 100644
--- a/unittest/scripts/auto/js_shell/scripts/authentication_kerberos_norecord.js
+++ b/unittest/scripts/auto/js_shell/scripts/authentication_kerberos_norecord.js
@@ -1,9 +1,9 @@
-//@ {hasAuthEnvironment('KERBEROS') && __version_num >= 80026 && __os_type != 'macos'}
+//@ {hasAuthEnvironment('KERBEROS') && __version_num >= 80026}
 
 //@<> Setup
 if (debugAuthPlugins()) {
-  dba.verbose = 1;
-  testutil.setenv("AUTHENTICATION_KERBEROS_CLIENT_LOG", "5");
+    dba.verbose = 1;
+    testutil.setenv("AUTHENTICATION_KERBEROS_CLIENT_LOG", "5");
 }
 
 var server_conf = getAuthServerConfig('KERBEROS');
@@ -36,15 +36,15 @@ var test_list = {
     "SELECT @@local.external_user": null
 };
 
-// Cleans the Kerberos cache
-testutil.callMysqlsh(["--py", "-i", "-e", `import os;os.system('${__os_type == 'windows' ? 'klist purge' : 'kdestroy'}')`]);
+//@<> Cleans the Kerberos cache
+destroy_kerberos_cache();
 
 //@<> WL14553-TSFR_8_2 - Kerberos session with no user/password, should fail as there's no cached TGT {kerberos_available}
 args = ["--mysql", "--host=localhost",
     `--port=${__mysql_sandbox_port1}`,
     '--schema=test_user_db',
     '--auth-method=authentication_kerberos_client',
-    "--credential-store-helper=plaintext"]
+    "--credential-store-helper=<disabled>"]
 
 // WL14553-TSFR_9_4 - No user/password provided
 testutil.callMysqlsh(args.concat(["-i", "--sql", "-e", "SELECT current_user()"]));
diff --git a/unittest/scripts/auto/js_shell/scripts/authentication_ldap_kerberos_norecord.js b/unittest/scripts/auto/js_shell/scripts/authentication_ldap_kerberos_norecord.js
index 232d1bdf52..babe1ce3b3 100644
--- a/unittest/scripts/auto/js_shell/scripts/authentication_ldap_kerberos_norecord.js
+++ b/unittest/scripts/auto/js_shell/scripts/authentication_ldap_kerberos_norecord.js
@@ -2,7 +2,7 @@
 
 //@<> Setup
 if (debugAuthPlugins()) {
-  testutil.setenv("AUTHENTICATION_LDAP_CLIENT_LOG", "5");
+    testutil.setenv("AUTHENTICATION_LDAP_CLIENT_LOG", "5");
 }
 
 testutil.deployRawSandbox(__mysql_sandbox_port1, 'root', getAuthServerConfig('LDAP_KERBEROS'));
@@ -35,15 +35,15 @@ var test_list = {
     "SELECT @@local.proxy_user": `'${LDAP_KERBEROS_USER}@${LDAP_KERBEROS_DOMAIN}'@'%'`,
 };
 
-// Cleans the Kerberos cache
-testutil.callMysqlsh(["--py", "-i", "-e", `import os;os.system('${__os_type == 'windows' ? 'klist purge' : 'kdestroy'}')`]);
+//@<> Cleans the Kerberos cache
+destroy_kerberos_cache();
 
 //@<> WL14553-TSFR_8_2 - Kerberos session with no user/password, should fail as there's no cached TGT {ldap_kerberos_available}
 args = ["--mysql", "--host=localhost",
     `--port=${__mysql_sandbox_port1}`,
     '--schema=test_user_db',
     '--auth-method=authentication_ldap_sasl_client',
-    "--credential-store-helper=plaintext"]
+    "--credential-store-helper=<disabled>"]
 
 // WL14553-TSFR_9_4 - No user/password provided
 testutil.callMysqlsh(args.concat(["-i", "-e", "SELECT 1"]));
diff --git a/unittest/scripts/auto/js_shell/scripts/authentication_openid_connect_norecord.js b/unittest/scripts/auto/js_shell/scripts/authentication_openid_connect_norecord.js
index ebbb50b74b..51c8f77e16 100644
--- a/unittest/scripts/auto/js_shell/scripts/authentication_openid_connect_norecord.js
+++ b/unittest/scripts/auto/js_shell/scripts/authentication_openid_connect_norecord.js
@@ -28,7 +28,7 @@ try {
 
     if (openid_available) {
         // setup
-        session.runSql(`SET GLOBAL authentication_openid_connect_configuration='file://${cnfg_path}'`);
+        session.runSql('SET GLOBAL authentication_openid_connect_configuration=?', [`file://${cnfg_path}`]);
         session.runSql(`CREATE USER '${OPENID_CONNECT_USER}' IDENTIFIED WITH authentication_openid_connect BY '{"identity_provider":"${OPENID_CONNECT_PROVIDER}","user":"${OPENID_CONNECT_SUBJECT}"}';`);
         session.runSql("CREATE USER 'other_user' IDENTIFIED BY 'mypwd'");
         session.runSql(`GRANT ALL PRIVILEGES ON *.* TO '${OPENID_CONNECT_USER}'`);
@@ -167,7 +167,7 @@ session.close()
 //@<> WL16470#FR3_1 - config - proper user, proper auth, invalid token file
 {
     var config_file = testutil.catFile(mysqlsh_cnfg_replace_path);
-    config_file = config_file.replace("[REPLACE]", token_path_invalid);
+    config_file = config_file.replace("[REPLACE]", token_path_invalid.replace(/\\/g, "/"));
     testutil.createFile(mysqlsh_cnfg_path, config_file);
 }
 testutil.callMysqlsh([
@@ -230,7 +230,7 @@ session.close();
 //@<> WL16470#FR3_2 - config - proper user, proper auth, valid token file {openid_available}
 {
     var config_file = testutil.catFile(mysqlsh_cnfg_replace_path);
-    config_file = config_file.replace("[REPLACE]", token_path);
+    config_file = config_file.replace("[REPLACE]", token_path.replace(/\\/g, "/"));
     testutil.createFile(mysqlsh_cnfg_path, config_file);
 }
 testutil.callMysqlsh([
@@ -249,7 +249,7 @@ EXPECT_OUTPUT_CONTAINS(`${OPENID_CONNECT_USER}`);
 //@<> WL16470#FR5_1 - config - another user, other password, openid-auth auth, valid token file {openid_available}
 {
     var config_file = testutil.catFile(mysqlsh_cnfg_replace_path);
-    config_file = config_file.replace("[REPLACE]", token_path);
+    config_file = config_file.replace("[REPLACE]", token_path.replace(/\\/g, "/"));
     config_file = config_file + '\npassword=mypwd';
     testutil.createFile(mysqlsh_cnfg_path, config_file);
 }
diff --git a/unittest/scripts/auto/js_shell/scripts/authentication_webauthn_device_norecord.js b/unittest/scripts/auto/js_shell/scripts/authentication_webauthn_device_norecord.js
index 8ee2344fc8..ac63e1a328 100644
--- a/unittest/scripts/auto/js_shell/scripts/authentication_webauthn_device_norecord.js
+++ b/unittest/scripts/auto/js_shell/scripts/authentication_webauthn_device_norecord.js
@@ -23,13 +23,13 @@ try {
 //@<> WL16770#FR1 wrong type value {fido_available}
 EXPECT_THROWS(function(){
     shell.connect(`webauthn_test:mypwd@localhost:${__mysql_sandbox_port1}?plugin-authentication-webauthn-device=testvalue`);
-}, "Argument #1: Invalid URI: The value of 'plugin-authentication-webauthn-device' must be an integer larger than 0.");
+}, "Argument #1: Invalid URI: The value of 'plugin-authentication-webauthn-device' must be an integer equal to or greater than 0.");
 WIPE_OUTPUT();
 
 //@<> WL16770#FR1 negative value {fido_available}
 EXPECT_THROWS(function(){
     shell.connect(`webauthn_test:mypwd@localhost:${__mysql_sandbox_port1}?plugin-authentication-webauthn-device=-2`);
-}, "Argument #1: Invalid URI: The value of 'plugin-authentication-webauthn-device' must be an integer larger than 0.");
+}, "Argument #1: Invalid URI: The value of 'plugin-authentication-webauthn-device' must be an integer equal to or greater than 0.");
 WIPE_OUTPUT();
 
 //@<> Drops the sandbox
diff --git a/unittest/scripts/auto/js_shell/scripts/cli_cluster_help_norecord.js b/unittest/scripts/auto/js_shell/scripts/cli_cluster_help_norecord.js
index b1d74ee1fe..362b9e1890 100644
--- a/unittest/scripts/auto/js_shell/scripts/cli_cluster_help_norecord.js
+++ b/unittest/scripts/auto/js_shell/scripts/cli_cluster_help_norecord.js
@@ -72,9 +72,6 @@ callMysqlsh(["--", "cluster", "unfence-writes", "--help"])
 //@ CLI cluster add-replica-instance --help
 callMysqlsh(["--", "cluster", "add-replica-instance", "--help"])
 
-//@ CLI cluster routing-options --help
-callMysqlsh(["--", "cluster", "routing-options", "--help"])
-
 //@ CLI cluster router-options --help
 callMysqlsh(["--", "cluster", "router-options", "--help"])
 
diff --git a/unittest/scripts/auto/js_shell/scripts/cli_help_norecord.js b/unittest/scripts/auto/js_shell/scripts/cli_help_norecord.js
index 4d8b680c66..6a30ba04f5 100644
--- a/unittest/scripts/auto/js_shell/scripts/cli_help_norecord.js
+++ b/unittest/scripts/auto/js_shell/scripts/cli_help_norecord.js
@@ -254,3 +254,67 @@ callMysqlsh(["--", "util", "importTable", "-h"])
 
 //@ CLI util loadDump -h [USE:CLI util load-dump --help]
 callMysqlsh(["--", "util", "loadDump", "-h"])
+
+
+//@ CLI shell delete-all-secrets --help
+callMysqlsh(["--", "shell", "delete-all-secrets", "--help"])
+
+//@ CLI shell delete-secret --help
+callMysqlsh(["--", "shell", "delete-secret", "--help"])
+
+//@ CLI shell list-secrets --help
+callMysqlsh(["--", "shell", "list-secrets", "--help"])
+
+//@ CLI shell read-secret --help
+callMysqlsh(["--", "shell", "read-secret", "--help"])
+
+//@ CLI shell store-secret --help
+callMysqlsh(["--", "shell", "store-secret", "--help"])
+
+
+//@ CLI shell delete-all-secrets -h [USE: CLI shell delete-all-secrets --help]
+callMysqlsh(["--", "shell", "delete-all-secrets", "-h"])
+
+//@ CLI shell delete-secret -h [USE: CLI shell delete-secret --help]
+callMysqlsh(["--", "shell", "delete-secret", "-h"])
+
+//@ CLI shell list-secrets -h [USE: CLI shell list-secrets --help]
+callMysqlsh(["--", "shell", "list-secrets", "-h"])
+
+//@ CLI shell read-secret -h [USE: CLI shell read-secret --help]
+callMysqlsh(["--", "shell", "read-secret", "--help"])
+
+//@ CLI shell store-secret -h [USE: CLI shell store-secret --help]
+callMysqlsh(["--", "shell", "store-secret", "-h"])
+
+
+//@ CLI shell deleteAllSecrets --help [USE: CLI shell delete-all-secrets --help]
+callMysqlsh(["--", "shell", "deleteAllSecrets", "--help"])
+
+//@ CLI shell deleteSecret --help [USE: CLI shell delete-secret --help]
+callMysqlsh(["--", "shell", "deleteSecret", "--help"])
+
+//@ CLI shell listSecrets --help [USE: CLI shell list-secrets --help]
+callMysqlsh(["--", "shell", "listSecrets", "--help"])
+
+//@ CLI shell readSecret --help [USE: CLI shell read-secret --help]
+callMysqlsh(["--", "shell", "readSecret", "--help"])
+
+//@ CLI shell storeSecret --help [USE: CLI shell store-secret --help]
+callMysqlsh(["--", "shell", "storeSecret", "--help"])
+
+
+//@ CLI shell deleteAllSecrets -h [USE: CLI shell delete-all-secrets --help]
+callMysqlsh(["--", "shell", "deleteAllSecrets", "-h"])
+
+//@ CLI shell deleteSecret -h [USE: CLI shell delete-secret --help]
+callMysqlsh(["--", "shell", "deleteSecret", "-h"])
+
+//@ CLI shell listSecrets -h [USE: CLI shell list-secrets --help]
+callMysqlsh(["--", "shell", "listSecrets", "-h"])
+
+//@ CLI shell readSecret -h [USE: CLI shell read-secret --help]
+callMysqlsh(["--", "shell", "readSecret", "-h"])
+
+//@ CLI shell storeSecret -h [USE: CLI shell store-secret --help]
+callMysqlsh(["--", "shell", "storeSecret", "-h"])
diff --git a/unittest/scripts/auto/js_shell/scripts/cli_replicaset_help_norecord.js b/unittest/scripts/auto/js_shell/scripts/cli_replicaset_help_norecord.js
index c9deb8611f..76530400a9 100644
--- a/unittest/scripts/auto/js_shell/scripts/cli_replicaset_help_norecord.js
+++ b/unittest/scripts/auto/js_shell/scripts/cli_replicaset_help_norecord.js
@@ -27,9 +27,6 @@ callMysqlsh(["--", "rs", "remove-instance", "--help"])
 //@ CLI replicaset remove-router-metadata --help
 callMysqlsh(["--", "rs", "remove-router-metadata", "--help"])
 
-//@ CLI replicaset routing-options --help
-callMysqlsh(["--", "rs", "routing-options", "--help"])
-
 //@ CLI replicaset router-options --help
 callMysqlsh(["--", "rs", "router-options", "--help"])
 
diff --git a/unittest/scripts/auto/js_shell/scripts/dba_enable_x_protocol_norecord.js b/unittest/scripts/auto/js_shell/scripts/dba_enable_x_protocol_norecord.js
deleted file mode 100644
index baad667bc9..0000000000
--- a/unittest/scripts/auto/js_shell/scripts/dba_enable_x_protocol_norecord.js
+++ /dev/null
@@ -1,19 +0,0 @@
-//@ Installing the plugin via --dba enableXProtocol: plugin already enabled
-testutil.deployRawSandbox(__mysql_sandbox_port1, "root");
-testutil.callMysqlsh(['-i', '--uri', __sandbox_uri1, '--dba', 'enableXProtocol']);
-
-//@ Uninstalling the x plugin {VER(<8.0.11)}
-testutil.callMysqlsh(['--interactive=full', '--uri', __sandbox_uri1, '--sqlc', '-e', 'uninstall plugin mysqlx;']);
-
-//@ Attempt installing the plugin using a busy port {VER(<8.0.11)}
-let x_port = __mysql_sandbox_port1 * 10;
-testutil.deployRawSandbox(__mysql_sandbox_port2, "root", {mysqlx_port:x_port});
-testutil.callMysqlsh(['-i', '--uri', __sandbox_uri1, '--dba', 'enableXProtocol']);
-testutil.destroySandbox(__mysql_sandbox_port2);
-testutil.callMysqlsh(['--interactive=full', '--uri', __sandbox_uri1, '--sqlc', '-e', 'uninstall plugin mysqlx;']);
-
-//@ Re-installing the plugin via --dba enableXProtocol {VER(<8.0.11)}
-testutil.callMysqlsh(['-i', '--uri', __sandbox_uri1, '--dba', 'enableXProtocol']);
-
-//@<> Cleanup
-testutil.destroySandbox(__mysql_sandbox_port1);
diff --git a/unittest/scripts/auto/js_shell/scripts/interactive_timeout_norecord.js b/unittest/scripts/auto/js_shell/scripts/interactive_timeout_norecord.js
index 97e6a79ef1..eb0c31f31e 100644
--- a/unittest/scripts/auto/js_shell/scripts/interactive_timeout_norecord.js
+++ b/unittest/scripts/auto/js_shell/scripts/interactive_timeout_norecord.js
@@ -1,6 +1,6 @@
 //@<> Setup
 testutil.deployRawSandbox(__mysql_sandbox_port1, "root", {interactive_timeout: 1});
-if (__version_num >= 80000) {
+if (__version_num >= 80000 && __os_type != 'windows') {
     error = "The client was disconnected by the server because of inactivity. See wait_timeout and interactive_timeout for configuring this behavior.";
 } else {
     error = "Lost connection to MySQL server during query";
diff --git a/unittest/scripts/auto/js_shell/scripts/mysql_classic_session_help_norecord.js b/unittest/scripts/auto/js_shell/scripts/mysql_classic_session_help_norecord.js
index f9fb025986..46c57069f9 100644
--- a/unittest/scripts/auto/js_shell/scripts/mysql_classic_session_help_norecord.js
+++ b/unittest/scripts/auto/js_shell/scripts/mysql_classic_session_help_norecord.js
@@ -10,6 +10,9 @@ session.help();
 //@ Help on uri
 session.help('uri')
 
+//@ Help on getSqlMode
+session.help('getSqlMode')
+
 //@ Help on uri, \? [USE:Help on uri]
 \? classicsession.uri
 
diff --git a/unittest/scripts/auto/js_shell/scripts/shell_help_norecord.js b/unittest/scripts/auto/js_shell/scripts/shell_help_norecord.js
index e558c1fd1d..4883a5e952 100644
--- a/unittest/scripts/auto/js_shell/scripts/shell_help_norecord.js
+++ b/unittest/scripts/auto/js_shell/scripts/shell_help_norecord.js
@@ -77,12 +77,24 @@ shell.help("deleteAllCredentials")
 //@ Help on deleteAllCredentials, \? [USE:Help on deleteAllCredentials]
 \? deleteAllCredentials
 
+//@ Help on deleteAllSecrets
+shell.help("deleteAllSecrets")
+
+//@ Help on deleteAllSecrets, \? [USE: Help on deleteAllSecrets]
+\? deleteAllSecrets
+
 //@ Help on deleteCredential
 shell.help("deleteCredential")
 
 //@ Help on deleteCredential, \? [USE:Help on deleteCredential]
 \? shell.deleteCredential
 
+//@ Help on deleteSecret
+shell.help("deleteSecret")
+
+//@ Help on deleteSecret, \? [USE: Help on deleteSecret]
+\? shell.deleteSecret
+
 //@ Help on disablePager
 shell.help("disablePager")
 
@@ -119,6 +131,12 @@ shell.help("listCredentials")
 //@ Help on listCredentials, \? [USE:Help on listCredentials]
 \? shell.listCredentials
 
+//@ Help on listSecrets
+shell.help("listSecrets")
+
+//@ Help on listSecrets, \? [USE: Help on listSecrets]
+\? shell.listSecrets
+
 //@ Help on listSshConnections
 shell.help("listSshConnections")
 
@@ -149,6 +167,12 @@ shell.help("disconnect")
 //@ Help on disconnect [USE: Help on shell.disconnect]
 \? shell.disconnect
 
+//@ Help on readSecret
+shell.help("readSecret")
+
+//@ Help on readSecret, \? [USE: Help on readSecret]
+\? readSecret
+
 //@ Help on reconnect
 shell.help("reconnect")
 
@@ -208,6 +232,12 @@ shell.help("storeCredential")
 //@ Help on storeCredential, \? [USE:Help on storeCredential]
 \? storeCredential
 
+//@ Help on storeSecret
+shell.help("storeSecret")
+
+//@ Help on storeSecret, \? [USE: Help on storeSecret]
+\? storeSecret
+
 //@ BUG28393119 UNABLE TO GET HELP ON CONNECTION DATA, before session
 \? connection
 
diff --git a/unittest/scripts/auto/js_shell/scripts/shell_log_sql_norecord.js b/unittest/scripts/auto/js_shell/scripts/shell_log_sql_norecord.js
index f9272f84fc..68b53b917a 100644
--- a/unittest/scripts/auto/js_shell/scripts/shell_log_sql_norecord.js
+++ b/unittest/scripts/auto/js_shell/scripts/shell_log_sql_norecord.js
@@ -6,8 +6,7 @@ shell.connect(__sandbox_uri1);
 // Using 'unfiltered' logging option
 // =================================
 
-\option logSql = "unfiltered"
-\option dba.logSql = 0
+\option logSql = unfiltered
 
 //@<> Testing unfiltered sql logging with \use
 WIPE_SHELL_LOG();
diff --git a/unittest/scripts/auto/js_shell/scripts/shell_options_help_norecord.js b/unittest/scripts/auto/js_shell/scripts/shell_options_help_norecord.js
index 21f1576b63..510734b304 100644
--- a/unittest/scripts/auto/js_shell/scripts/shell_options_help_norecord.js
+++ b/unittest/scripts/auto/js_shell/scripts/shell_options_help_norecord.js
@@ -28,9 +28,6 @@
 //@ logLevel option help text
 \option -h logLevel
 
-//@ outputFormat option help text
-\option --help outputFormat
-
 //@ resultFormat option help text
 \option --help resultFormat
 
@@ -50,10 +47,6 @@
 // WL#11862 - FR6_5
 \option -h dba.gtidWaitTimeout
 
-//@ dba.logSql option help text
-// WL#13294
-\option -h dba.logSql
-
 //@ Verify the help text when using filter
 \option --help history
 
diff --git a/unittest/scripts/auto/js_shell/scripts/shell_options_persists_norecord.js b/unittest/scripts/auto/js_shell/scripts/shell_options_persists_norecord.js
index 243efe1c64..8c48e4c714 100644
--- a/unittest/scripts/auto/js_shell/scripts/shell_options_persists_norecord.js
+++ b/unittest/scripts/auto/js_shell/scripts/shell_options_persists_norecord.js
@@ -66,13 +66,6 @@ os.loadTextFile(options_file);
 shell.options.unsetPersist("logLevel");
 shell.options["logLevel"]
 
-//@ outputFormat update and set back to default using shell.options
-shell.options.setPersist("outputFormat", "tabbed");
-shell.options["outputFormat"]
-os.loadTextFile(options_file);
-shell.options.unsetPersist("outputFormat")
-shell.options["outputFormat"]
-
 //@ resultFormat update and set back to default using shell.options
 shell.options.setPersist("resultFormat", "json/raw");
 shell.options["resultFormat"]
@@ -130,14 +123,6 @@ os.loadTextFile(options_file);
 shell.options.unsetPersist("dba.restartWaitTimeout");
 shell.options["dba.restartWaitTimeout"]
 
-//@ dba.logSql update and set back to default using shell.options
-// WL#13294
-shell.options.setPersist("dba.logSql", "1");
-shell.options["dba.logSql"]
-os.loadTextFile(options_file);
-shell.options.unsetPersist("dba.logSql");
-shell.options["dba.logSql"]
-
 //@ autocomplete.nameCache update and set back to default using \option
 \option --persist autocomplete.nameCache = false
 \option autocomplete.nameCache
@@ -199,13 +184,6 @@ os.loadTextFile(options_file);
 \option --unset --persist logLevel
 \option logLevel
 
-//@ outputFormat update and set back to default using \option
-\option --persist outputFormat = tabbed
-\option outputFormat
-os.loadTextFile(options_file);
-\option --unset --persist outputFormat
-\option outputFormat
-
 //@ resultFormat update and set back to default using \option
 \option --persist resultFormat = json/raw
 \option resultFormat
@@ -263,14 +241,6 @@ os.loadTextFile(options_file);
 \option --unset --persist dba.restartWaitTimeout
 \option dba.restartWaitTimeout
 
-//@ dba.logSql update and set back to default using \option
-// WL#13294
-\option --persist dba.logSql = 2
-\option dba.logSql
-os.loadTextFile(options_file);
-\option --unset --persist dba.logSql
-\option dba.logSql
-
 //@ credentialStore.helper update and set back to default using \option
 \option --persist credentialStore.helper = plaintext
 \option credentialStore.helper
@@ -353,16 +323,6 @@ shell.options.unset(InvalidOption)
 \option dba.restartWaitTimeout = 1
 \option --unset dba.restartWaitTimeout
 
-//@ Verify option dba.logSql
-// WL#13294
-\option dba.logSql = 0.2
-\option dba.logSql = -1
-\option dba.logSql = 3
-\option dba.logSql = "not valid"
-\option dba.logSql = 0
-\option dba.logSql = 2
-\option --unset dba.logSql
-
 //@ Verify option verbose
 \option verbose = 0.2
 \option verbose = -1
@@ -488,14 +448,12 @@ function test_cli_option_update(option, default_value, test_value) {
 //@<> WL14297 - TSFR_3_1_1 - Updating Shell Options using CLI integration
 //test_cli_option_update('autocomplete.nameCache', true, false);
 test_cli_option_update('dba.gtidWaitTimeout', 60, 30);
-test_cli_option_update('dba.logSql', 0, 2);
 test_cli_option_update('dba.gtidWaitTimeout', 60, 30);
 test_cli_option_update('defaultCompress', false, true);
 test_cli_option_update('devapi.dbObjectHandles', true, false);
 test_cli_option_update('history.autoSave', true, false);
 test_cli_option_update('history.maxSize', 1000, 20);
 test_cli_option_update('logLevel', 5, 1);
-test_cli_option_update('outputFormat', "table", "vertical");
 test_cli_option_update('resultFormat', "table", "vertical");
 test_cli_option_update('showColumnTypeInfo', false, true);
 test_cli_option_update('showWarnings', true, false);
diff --git a/unittest/scripts/auto/js_shell/scripts/shell_register_sql_handler_norecord.js b/unittest/scripts/auto/js_shell/scripts/shell_register_sql_handler_norecord.js
index 2fb77f0a9e..39979ebc60 100644
--- a/unittest/scripts/auto/js_shell/scripts/shell_register_sql_handler_norecord.js
+++ b/unittest/scripts/auto/js_shell/scripts/shell_register_sql_handler_norecord.js
@@ -226,6 +226,39 @@ callMysqlsh([__mysqluripwd, "--js", "--disable-plugins", "-i", "--tabbed", "-e",
 EXPECT_STDOUT_CONTAINS("You have an error in your SQL syntax;")
 EXPECT_STDOUT_NOT_CONTAINS(`====> SQL HANDLER:`)
 
+//@<> BUG#37196079 - SQL handler callbacks received SQL query still containing the placeholders
+plugin_code = `
+function my_handler(session, sql) {
+    println("====> SQL HANDLER:", sql);
+}
+
+shell.registerSqlHandler("placeholdersHandler", "Handler with placeholders", ["SELECT "], my_handler);
+`
+testutil.createFile(plugin_path, plugin_code);
+
+callMysqlsh([__mysqluripwd, "--js", "-e", "session.runSql('select 1', [])"]);
+EXPECT_STDOUT_CONTAINS(`
+====> SQL HANDLER: select 1
+`);
+WIPE_OUTPUT();
+
+callMysqlsh([__mysqluripwd, "--js", "-e", "session.runSql('select ? from dual', [1])"]);
+EXPECT_STDOUT_CONTAINS(`
+====> SQL HANDLER: select 1 from dual
+`);
+WIPE_OUTPUT();
+
+callMysqlsh([__mysqluripwd, "--js", "-e", "session.runSql('select ?,?, ? from DUAL', ['a', 2, 'c'])"]);
+EXPECT_STDOUT_CONTAINS(`
+====> SQL HANDLER: select 'a',2, 'c' from DUAL
+`);
+WIPE_OUTPUT();
+
+callMysqlsh([__mysqluripwd, "--js", "-e", "session.runSql('select ? from dual', [])"])
+EXPECT_STDOUT_CONTAINS(`
+Insufficient number of values for placeholders in query (ArgumentError)
+`)
+WIPE_OUTPUT()
+
 //@<> Finalization
 testutil.rmdir(plugins_path, true)
-
diff --git a/unittest/scripts/auto/js_shell/scripts/sql_show_column_type_info_norecord.js b/unittest/scripts/auto/js_shell/scripts/sql_show_column_type_info_norecord.js
index 347f86d60a..56b342cf48 100644
--- a/unittest/scripts/auto/js_shell/scripts/sql_show_column_type_info_norecord.js
+++ b/unittest/scripts/auto/js_shell/scripts/sql_show_column_type_info_norecord.js
@@ -1,6 +1,6 @@
 shell.connect(__uripwd);
 \sql
-\option outputFormat = tabbed
+\option resultFormat = tabbed
 
 //@ Setup
 drop database if exists scti_test;
diff --git a/unittest/scripts/auto/js_shell/scripts/util_import_table_norecord.js b/unittest/scripts/auto/js_shell/scripts/util_import_table_norecord.js
index 548886ffb9..ded91f59d9 100644
--- a/unittest/scripts/auto/js_shell/scripts/util_import_table_norecord.js
+++ b/unittest/scripts/auto/js_shell/scripts/util_import_table_norecord.js
@@ -118,11 +118,11 @@ util.importTable(__import_data_path + '/world_x_cities.csv', {
 
 var keyname = testutil.versionCheck(__version, '<', '8.0.19') ? `'PRIMARY'` : `'cities.PRIMARY'`;
 
-EXPECT_STDOUT_CONTAINS("WARNING: world_x_cities.csv error 1062: Duplicate entry '1' for key " + keyname);
-EXPECT_STDOUT_CONTAINS("WARNING: world_x_cities.csv error 1062: Duplicate entry '2' for key " + keyname);
-EXPECT_STDOUT_CONTAINS("WARNING: world_x_cities.csv error 1062: Duplicate entry '3' for key " + keyname);
-EXPECT_STDOUT_CONTAINS("WARNING: world_x_cities.csv error 1062: Duplicate entry '4' for key " + keyname);
-EXPECT_STDOUT_CONTAINS("WARNING: world_x_cities.csv error 1062: Duplicate entry '5' for key " + keyname);
+EXPECT_STDOUT_CONTAINS("WARNING: LOAD DATA error for `wl12193`.`cities` in file 'world_x_cities.csv', error 1062: Duplicate entry '1' for key " + keyname);
+EXPECT_STDOUT_CONTAINS("WARNING: LOAD DATA error for `wl12193`.`cities` in file 'world_x_cities.csv', error 1062: Duplicate entry '2' for key " + keyname);
+EXPECT_STDOUT_CONTAINS("WARNING: LOAD DATA error for `wl12193`.`cities` in file 'world_x_cities.csv', error 1062: Duplicate entry '3' for key " + keyname);
+EXPECT_STDOUT_CONTAINS("WARNING: LOAD DATA error for `wl12193`.`cities` in file 'world_x_cities.csv', error 1062: Duplicate entry '4' for key " + keyname);
+EXPECT_STDOUT_CONTAINS("WARNING: LOAD DATA error for `wl12193`.`cities` in file 'world_x_cities.csv', error 1062: Duplicate entry '5' for key " + keyname);
 EXPECT_STDOUT_CONTAINS("File '" + filename_for_output(__import_data_path + "/world_x_cities.csv") + "' (250.53 KB) was imported in ");
 EXPECT_STDOUT_CONTAINS("Total rows affected in " + target_schema + ".cities: Records: 4079  Deleted: 0  Skipped: 4079  Warnings: 4079");
 
@@ -133,11 +133,11 @@ util.importTable(__import_data_path + '/world_x_cities_header.csv', {
 });
 
 EXPECT_STDOUT_CONTAINS("wl12193.cities: Records: 4079  Deleted: 0  Skipped: 4079  Warnings: 4079");
-EXPECT_STDOUT_CONTAINS("WARNING: world_x_cities_header.csv error 1062: Duplicate entry '1' for key " + keyname);
-EXPECT_STDOUT_CONTAINS("WARNING: world_x_cities_header.csv error 1062: Duplicate entry '2' for key " + keyname);
-EXPECT_STDOUT_CONTAINS("WARNING: world_x_cities_header.csv error 1062: Duplicate entry '3' for key " + keyname);
-EXPECT_STDOUT_CONTAINS("WARNING: world_x_cities_header.csv error 1062: Duplicate entry '4' for key " + keyname);
-EXPECT_STDOUT_CONTAINS("WARNING: world_x_cities_header.csv error 1062: Duplicate entry '5' for key " + keyname);
+EXPECT_STDOUT_CONTAINS("WARNING: LOAD DATA error for `wl12193`.`cities` in file 'world_x_cities_header.csv', error 1062: Duplicate entry '1' for key " + keyname);
+EXPECT_STDOUT_CONTAINS("WARNING: LOAD DATA error for `wl12193`.`cities` in file 'world_x_cities_header.csv', error 1062: Duplicate entry '2' for key " + keyname);
+EXPECT_STDOUT_CONTAINS("WARNING: LOAD DATA error for `wl12193`.`cities` in file 'world_x_cities_header.csv', error 1062: Duplicate entry '3' for key " + keyname);
+EXPECT_STDOUT_CONTAINS("WARNING: LOAD DATA error for `wl12193`.`cities` in file 'world_x_cities_header.csv', error 1062: Duplicate entry '4' for key " + keyname);
+EXPECT_STDOUT_CONTAINS("WARNING: LOAD DATA error for `wl12193`.`cities` in file 'world_x_cities_header.csv', error 1062: Duplicate entry '5' for key " + keyname);
 EXPECT_STDOUT_CONTAINS("File '" + filename_for_output(__import_data_path + "/world_x_cities_header.csv") + "' (250.53 KB) was imported in ");
 EXPECT_STDOUT_CONTAINS("Total rows affected in " + target_schema + ".cities: Records: 4079  Deleted: 0  Skipped: 4079  Warnings: 4079");
 
@@ -206,11 +206,11 @@ var rc = callMysqlsh([uri,
 keyname = testutil.versionCheck(__version, '<', '8.0.19') ? `'PRIMARY'` : `'document_store.PRIMARY'`;
 
 EXPECT_STDOUT_CONTAINS("wl12193.document_store: Records: 25359  Deleted: 0  Skipped: 25359  Warnings: 25359");
-EXPECT_STDOUT_CONTAINS("WARNING: primer-dataset-id.json error 1062: Duplicate entry '000000000001' for key " + keyname);
-EXPECT_STDOUT_CONTAINS("WARNING: primer-dataset-id.json error 1062: Duplicate entry '000000000002' for key " + keyname);
-EXPECT_STDOUT_CONTAINS("WARNING: primer-dataset-id.json error 1062: Duplicate entry '000000000003' for key " + keyname);
-EXPECT_STDOUT_CONTAINS("WARNING: primer-dataset-id.json error 1062: Duplicate entry '000000000004' for key " + keyname);
-EXPECT_STDOUT_CONTAINS("WARNING: primer-dataset-id.json error 1062: Duplicate entry '000000000005' for key " + keyname);
+EXPECT_STDOUT_CONTAINS("WARNING: LOAD DATA error for `wl12193`.`document_store` in file 'primer-dataset-id.json', error 1062: Duplicate entry '000000000001' for key " + keyname);
+EXPECT_STDOUT_CONTAINS("WARNING: LOAD DATA error for `wl12193`.`document_store` in file 'primer-dataset-id.json', error 1062: Duplicate entry '000000000002' for key " + keyname);
+EXPECT_STDOUT_CONTAINS("WARNING: LOAD DATA error for `wl12193`.`document_store` in file 'primer-dataset-id.json', error 1062: Duplicate entry '000000000003' for key " + keyname);
+EXPECT_STDOUT_CONTAINS("WARNING: LOAD DATA error for `wl12193`.`document_store` in file 'primer-dataset-id.json', error 1062: Duplicate entry '000000000004' for key " + keyname);
+EXPECT_STDOUT_CONTAINS("WARNING: LOAD DATA error for `wl12193`.`document_store` in file 'primer-dataset-id.json', error 1062: Duplicate entry '000000000005' for key " + keyname);
 EXPECT_STDOUT_CONTAINS("File '" + filename_for_output(__import_data_path + "/primer-dataset-id.json") + "' (11.29 MB) was imported in ");
 EXPECT_STDOUT_CONTAINS("Total rows affected in " + target_schema + ".document_store: Records: 25359  Deleted: 0  Skipped: 25359  Warnings: 25359");
 
diff --git a/unittest/scripts/auto/js_shell/scripts/util_load_dump_norecord.js b/unittest/scripts/auto/js_shell/scripts/util_load_dump_norecord.js
index 12104ba221..aa8187d01e 100644
--- a/unittest/scripts/auto/js_shell/scripts/util_load_dump_norecord.js
+++ b/unittest/scripts/auto/js_shell/scripts/util_load_dump_norecord.js
@@ -174,11 +174,12 @@ if(__version_num<80000) {
 testutil.deploySandbox(__mysql_sandbox_port1, "root", {
   loose_innodb_directories: __tmp_dir+"/ldtest/datadirs",
   early_plugin_load: "keyring_file."+(__os_type=="windows"? "dll":"so"),
-  keyring_file_data: __tmp_dir+"/ldtest/datadirs/keyring"
+  keyring_file_data: __tmp_dir+"/ldtest/datadirs/keyring",
+  innodb_doublewrite: "OFF"
 });
 
 shell.connect(__sandbox_uri1);
-
+session.runSql("/*!80021 alter instance disable innodb redo_log */")
 
 // change root password
 session.runSql("SET PASSWORD FOR root@localhost = 'old'");
diff --git a/unittest/scripts/auto/js_shell/validation/cli_cluster_help_norecord.js b/unittest/scripts/auto/js_shell/validation/cli_cluster_help_norecord.js
index 3760c70d3c..0c551cf406 100644
--- a/unittest/scripts/auto/js_shell/validation/cli_cluster_help_norecord.js
+++ b/unittest/scripts/auto/js_shell/validation/cli_cluster_help_norecord.js
@@ -69,12 +69,6 @@ The following operations are available at 'cluster':
    routing-guidelines
       Lists the Routing Guidelines defined for the Cluster.
 
-   routing-options
-      Lists the Cluster Routers configuration options.
-
-      ATTENTION: This function is deprecated and will be removed in a future
-                 release of MySQL Shell. Use Cluster.routerOptions() instead.
-
    set-instance-option
       Changes the value of an option in a Cluster member.
 
@@ -315,21 +309,11 @@ RETURNS
       Nothing.
 
 OPTIONS
---addInstances[:<type>]=<value>
-            List with the connection data of the new active instances to add to
-            the metadata, or "auto" to automatically add missing instances to
-            the metadata. Deprecated.
-
 --addUnmanaged=<bool>
             Boolean. Set to true to automatically add newly discovered
             instances, i.e. already part of the replication topology but not
             managed in the Cluster, to the metadata. Defaults to false.
 
---removeInstances[:<type>]=<value>
-            List with the connection data of the obsolete instances to remove
-            from the metadata, or "auto" to automatically remove obsolete
-            instances from the metadata. Deprecated.
-
 --removeObsolete=<bool>
             Boolean. Set to true to automatically remove all obsolete
             instances, i.e. no longer part of the replication topology, from
@@ -597,20 +581,6 @@ OPTIONS
             established. If reached, the operation is rolled-back. Default is 0
             (no timeout).
 
-//@<OUT> CLI cluster routing-options --help
-NAME
-      routing-options - Lists the Cluster Routers configuration options.
-
-SYNTAX
-      cluster routing-options [<router>]
-
-WHERE
-      router: Identifier of the router instance to query for the options.
-
-RETURNS
-      A JSON object describing the configuration options of all router
-      instances of the Cluster and its global options or just the given Router.
-
 //@<OUT> CLI cluster router-options --help
 NAME
       router-options - Lists the configuration options of the Cluster's
@@ -732,3 +702,5 @@ RETURNS
 
 OPTIONS
 --force=<bool>
+
+--rename=<str>
diff --git a/unittest/scripts/auto/js_shell/validation/cli_clusterset_help_norecord.js b/unittest/scripts/auto/js_shell/validation/cli_clusterset_help_norecord.js
index 7203451c01..4b29abd71a 100644
--- a/unittest/scripts/auto/js_shell/validation/cli_clusterset_help_norecord.js
+++ b/unittest/scripts/auto/js_shell/validation/cli_clusterset_help_norecord.js
@@ -48,13 +48,6 @@ The following operations are available at 'clusterset':
    routing-guidelines
       Lists the Routing Guidelines defined for the ClusterSet.
 
-   routing-options
-      Lists the ClusterSet Routers configuration options.
-
-      ATTENTION: This function is deprecated and will be removed in a future
-                 release of MySQL Shell. Use ClusterSet.routerOptions()
-                 instead.
-
    set-option
       Changes the value of an option for the whole ClusterSet.
 
@@ -482,3 +475,5 @@ RETURNS
 
 OPTIONS
 --force=<bool>
+
+--rename=<str>
diff --git a/unittest/scripts/auto/js_shell/validation/cli_dba_help_norecord.js b/unittest/scripts/auto/js_shell/validation/cli_dba_help_norecord.js
index c8f8e182d8..f9438cb4cd 100644
--- a/unittest/scripts/auto/js_shell/validation/cli_dba_help_norecord.js
+++ b/unittest/scripts/auto/js_shell/validation/cli_dba_help_norecord.js
@@ -374,6 +374,10 @@ OPTIONS
             List of MySQL configuration options to write to the my.cnf file, as
             option=value strings.
 
+--mysqldPath=<str>
+            Path to the mysqld binary, or the MySQL installation root. Used to
+            locate the server binary for sandbox deployment.
+
 --password=<str>
             Password for the MySQL root user on the new instance.
 
diff --git a/unittest/scripts/auto/js_shell/validation/cli_help_norecord.js b/unittest/scripts/auto/js_shell/validation/cli_help_norecord.js
index 7546615575..bf59035a22 100644
--- a/unittest/scripts/auto/js_shell/validation/cli_help_norecord.js
+++ b/unittest/scripts/auto/js_shell/validation/cli_help_norecord.js
@@ -51,9 +51,15 @@ The following operations are available at 'shell':
    delete-all-credentials
       Deletes all credentials managed by the configured helper.
 
+   delete-all-secrets
+      Deletes all secrets managed by the configured helper.
+
    delete-credential
       Deletes credential for the given URL using the configured helper.
 
+   delete-secret
+      Deletes secret for the given key using the configured helper.
+
    list-credential-helpers
       Returns a list of strings, where each string is a name of a helper
       available on the current platform.
@@ -61,15 +67,24 @@ The following operations are available at 'shell':
    list-credentials
       Retrieves a list of all URLs stored by the configured helper.
 
+   list-secrets
+      Retrieves a list of all secrets' keys stored by the configured helper.
+
    list-sql-handlers
       Lists the name and description of any registered SQL handlers.
 
+   read-secret
+      Reads secret for the given key using the configured helper.
+
    status
       Shows connection status info for the shell.
 
    store-credential
       Stores given credential using the configured helper.
 
+   store-secret
+      Stores given secret using the configured helper.
+
 //@<OUT> CLI shell options --help
 The following operations are available at 'shell options':
 
@@ -81,10 +96,20 @@ The following operations are available at 'shell options':
       configuration file.
 
 //@<OUT> CLI util --help
+?{__has_util_plugin}
+The following objects provide command line operations at 'util':
+?{}
+?{!__has_util_plugin}
 The following object provides command line operations at 'util':
+?{}
 
    debug
       Debugging and diagnostic utilities.
+?{__has_util_plugin}
+
+   visual
+      Visualization utilities.
+?{}
 
 The following operations are available at 'util':
 
@@ -903,8 +928,8 @@ OPTIONS
 --ociAuth=<str>
             Use the specified authentication method when connecting to the OCI.
             Allowed values: api_key (used when not explicitly set),
-            instance_principal, resource_principal, security_token. Default:
-            not set.
+            instance_obo_user, instance_principal, resource_principal,
+            security_token. Default: not set.
 
 --ociConfigFile=<str>
             Use the specified OCI configuration file instead of the one at the
@@ -1136,8 +1161,8 @@ OPTIONS
 --ociAuth=<str>
             Use the specified authentication method when connecting to the OCI.
             Allowed values: api_key (used when not explicitly set),
-            instance_principal, resource_principal, security_token. Default:
-            not set.
+            instance_obo_user, instance_principal, resource_principal,
+            security_token. Default: not set.
 
 --ociConfigFile=<str>
             Use the specified OCI configuration file instead of the one at the
@@ -1383,8 +1408,8 @@ OPTIONS
 --ociAuth=<str>
             Use the specified authentication method when connecting to the OCI.
             Allowed values: api_key (used when not explicitly set),
-            instance_principal, resource_principal, security_token. Default:
-            not set.
+            instance_obo_user, instance_principal, resource_principal,
+            security_token. Default: not set.
 
 --ociConfigFile=<str>
             Use the specified OCI configuration file instead of the one at the
@@ -1597,8 +1622,8 @@ OPTIONS
 --ociAuth=<str>
             Use the specified authentication method when connecting to the OCI.
             Allowed values: api_key (used when not explicitly set),
-            instance_principal, resource_principal, security_token. Default:
-            not set.
+            instance_obo_user, instance_principal, resource_principal,
+            security_token. Default: not set.
 
 --ociConfigFile=<str>
             Use the specified OCI configuration file instead of the one at the
@@ -1756,8 +1781,8 @@ OPTIONS
 --ociAuth=<str>
             Use the specified authentication method when connecting to the OCI.
             Allowed values: api_key (used when not explicitly set),
-            instance_principal, resource_principal, security_token. Default:
-            not set.
+            instance_obo_user, instance_principal, resource_principal,
+            security_token. Default: not set.
 
 --ociConfigFile=<str>
             Use the specified OCI configuration file instead of the one at the
@@ -1981,8 +2006,8 @@ OPTIONS
 --ociAuth=<str>
             Use the specified authentication method when connecting to the OCI.
             Allowed values: api_key (used when not explicitly set),
-            instance_principal, resource_principal, security_token. Default:
-            not set.
+            instance_obo_user, instance_principal, resource_principal,
+            security_token. Default: not set.
 
 --ociConfigFile=<str>
             Use the specified OCI configuration file instead of the one at the
@@ -2095,8 +2120,8 @@ OPTIONS
 --ociAuth=<str>
             Use the specified authentication method when connecting to the OCI.
             Allowed values: api_key (used when not explicitly set),
-            instance_principal, resource_principal, security_token. Default:
-            not set.
+            instance_obo_user, instance_principal, resource_principal,
+            security_token. Default: not set.
 
 --ociConfigFile=<str>
             Use the specified OCI configuration file instead of the one at the
@@ -2349,8 +2374,8 @@ OPTIONS
 --ociAuth=<str>
             Use the specified authentication method when connecting to the OCI.
             Allowed values: api_key (used when not explicitly set),
-            instance_principal, resource_principal, security_token. Default:
-            not set.
+            instance_obo_user, instance_principal, resource_principal,
+            security_token. Default: not set.
 
 --ociConfigFile=<str>
             Use the specified OCI configuration file instead of the one at the
@@ -2431,3 +2456,54 @@ OPTIONS
             tables are processed the command will either wait for more data,
             the dump is marked as completed or the given timeout (in seconds)
             passes. <= 0 disables waiting. Default: 0.
+
+//@<OUT> CLI shell delete-all-secrets --help
+NAME
+      delete-all-secrets - Deletes all secrets managed by the configured
+                           helper.
+
+SYNTAX
+      shell delete-all-secrets
+
+//@<OUT> CLI shell delete-secret --help
+NAME
+      delete-secret - Deletes secret for the given key using the configured
+                      helper.
+
+SYNTAX
+      shell delete-secret <key>
+
+WHERE
+      key: A key of the secret to delete.
+
+//@<OUT> CLI shell list-secrets --help
+NAME
+      list-secrets - Retrieves a list of all secrets' keys stored by the
+                     configured helper.
+
+SYNTAX
+      shell list-secrets
+
+//@<OUT> CLI shell read-secret --help
+NAME
+      read-secret - Reads secret for the given key using the configured helper.
+
+SYNTAX
+      shell read-secret <key>
+
+WHERE
+      key: A key of the secret to read.
+
+RETURNS
+      Secret associated with the given key.
+
+//@<OUT> CLI shell store-secret --help
+NAME
+      store-secret - Stores given secret using the configured helper.
+
+SYNTAX
+      shell store-secret <key> [<value>]
+
+WHERE
+      key: A key that uniquely identifies the secret.
+      value: Value for the given key.
diff --git a/unittest/scripts/auto/js_shell/validation/cli_replicaset_help_norecord.js b/unittest/scripts/auto/js_shell/validation/cli_replicaset_help_norecord.js
index 3495c2dfb1..6b54cf3e07 100644
--- a/unittest/scripts/auto/js_shell/validation/cli_replicaset_help_norecord.js
+++ b/unittest/scripts/auto/js_shell/validation/cli_replicaset_help_norecord.js
@@ -52,13 +52,6 @@ The following operations are available at 'rs':
    routing-guidelines
       Lists the Routing Guidelines defined for the ReplicaSet.
 
-   routing-options
-      Lists the ReplicaSet Routers configuration options.
-
-      ATTENTION: This function is deprecated and will be removed in a future
-                 release of MySQL Shell. Use ReplicaSet.routerOptions()
-                 instead.
-
    set-instance-option
       Changes the value of an option in a ReplicaSet member.
 
@@ -277,21 +270,6 @@ WHERE
 RETURNS
       Nothing
 
-//@<OUT> CLI replicaset routing-options --help
-NAME
-      routing-options - Lists the ReplicaSet Routers configuration options.
-
-SYNTAX
-      rs routing-options [<router>]
-
-WHERE
-      router: Identifier of the router instance to query for the options.
-
-RETURNS
-      A JSON object describing the configuration options of all router
-      instances of the ReplicaSet and its global options or just the given
-      Router.
-
 //@<OUT> CLI replicaset router-options --help
 NAME
       router-options - Lists the configuration options of the ReplicaSet's
@@ -557,3 +535,5 @@ RETURNS
 
 OPTIONS
 --force=<bool>
+
+--rename=<str>
diff --git a/unittest/scripts/auto/js_shell/validation/dba_enable_x_protocol_norecord.js b/unittest/scripts/auto/js_shell/validation/dba_enable_x_protocol_norecord.js
deleted file mode 100644
index 0a48a49933..0000000000
--- a/unittest/scripts/auto/js_shell/validation/dba_enable_x_protocol_norecord.js
+++ /dev/null
@@ -1,16 +0,0 @@
-//@ Installing the plugin via --dba enableXProtocol: plugin already enabled
-|enableXProtocol: The X Protocol plugin is already enabled and listening for connections on port|
-
-//@ Uninstalling the x plugin
-|Query OK, 0 rows affected|
-
-
-//@<OUT> Attempt installing the plugin using a busy port {VER(<8.0.11)}
-enableXProtocol: Installing plugin mysqlx...
-enableXProtocol: Verifying plugin is active...
-enableXProtocol: WARNING: The X Protocol plugin is enabled, however a different server is listening for TCP connections at port <<<x_port>>>, check the MySQL Server log for more details
-
-//@<OUT> Re-installing the plugin via --dba enableXProtocol
-enableXProtocol: Installing plugin mysqlx...
-enableXProtocol: Verifying plugin is active...
-enableXProtocol: successfully installed the X protocol plugin!
diff --git a/unittest/scripts/auto/js_shell/validation/mysql_classic_session_help_norecord.js b/unittest/scripts/auto/js_shell/validation/mysql_classic_session_help_norecord.js
index 57d93cb53b..7c0c554313 100644
--- a/unittest/scripts/auto/js_shell/validation/mysql_classic_session_help_norecord.js
+++ b/unittest/scripts/auto/js_shell/validation/mysql_classic_session_help_norecord.js
@@ -25,9 +25,15 @@ FUNCTIONS
             Commits all the operations executed after a call to
             startTransaction().
 
+      getClientData(key)
+            Returns value associated with the session for the given key.
+
       getConnectionId()
             Retrieves the connection id for the current session.
 
+      getSqlMode()
+            Retrieves the SQL_MODE for the current session.
+
       getSshUri()
             Retrieves the SSH URI for the current session.
 
@@ -48,6 +54,9 @@ FUNCTIONS
             Executes a query and returns the corresponding ClassicResult
             object.
 
+      setClientData(key, value)
+            Associates a value with the session for the given key.
+
       setQueryAttributes()
             Defines query attributes that apply to the next statement sent to
             the server for execution.
@@ -62,6 +71,20 @@ NAME
 SYNTAX
       <ClassicSession>.uri
 
+//@<OUT> Help on getSqlMode
+NAME
+      getSqlMode - Retrieves the SQL_MODE for the current session.
+
+SYNTAX
+      <ClassicSession>.getSqlMode()
+
+RETURNS
+      Value of the SQL_MODE session variable.
+
+DESCRIPTION
+      Queries the value of the SQL_MODE session variable. If session tracking
+      of SQL_MODE is enabled, it will fetch its cached value.
+
 //@<OUT> Help on close
 NAME
       close - Closes the internal connection to the MySQL Server held on this
diff --git a/unittest/scripts/auto/js_shell/validation/mysql_help_norecord.js b/unittest/scripts/auto/js_shell/validation/mysql_help_norecord.js
index 95e86c9a14..beb4b2507d 100644
--- a/unittest/scripts/auto/js_shell/validation/mysql_help_norecord.js
+++ b/unittest/scripts/auto/js_shell/validation/mysql_help_norecord.js
@@ -43,6 +43,9 @@ FUNCTIONS
       splitScript(script)
             Split a SQL script into individual statements.
 
+      tokenizeStatement(statement)
+            Lexes a MySQL statement into a list of tokens.
+
       unquoteIdentifier(s)
             Unquote a MySQL identifier.
 
diff --git a/unittest/scripts/auto/js_shell/validation/mysqlsh_help_norecord.js b/unittest/scripts/auto/js_shell/validation/mysqlsh_help_norecord.js
index 0b8ba9294e..53cce6abfd 100644
--- a/unittest/scripts/auto/js_shell/validation/mysqlsh_help_norecord.js
+++ b/unittest/scripts/auto/js_shell/validation/mysqlsh_help_norecord.js
@@ -9,7 +9,6 @@ Usage: mysqlsh [OPTIONS] [URI]
        mysqlsh [OPTIONS] [URI] -f <path> [<script-args>...]
        mysqlsh [OPTIONS] [URI] --cluster|--replicaset
        mysqlsh [OPTIONS] [URI] -- <object> <method> [<method-args>...]
-       mysqlsh [OPTIONS] [URI] --dba enableXProtocol
 
   -?, --help                       Display this help and exit.
   --                               Triggers API Command Line integration, which
@@ -92,6 +91,8 @@ Usage: mysqlsh [OPTIONS] [URI]
   --zstd-compression-level=<int>   client/server protocol. Supported by X
                                    protocol and zstd compression in classic
                                    protocol.
+  --local-infile                   Enable LOAD DATA LOCAL INFILE. (classic
+                                   protocol only)
   -D, --schema=<name>              Schema to use.
   --database=<name>                Same as --schema.
   --register-factor=<name>         Specifies authentication factor, for which
@@ -167,11 +168,6 @@ Usage: mysqlsh [OPTIONS] [URI]
                                    be an integer between 1 and 8 or any of
                                    [none, internal, error, warning, info,
                                    debug, debug2, debug3] respectively.
-  --dba-log-sql[={0|1|2}]          Log SQL statements executed by AdminAPI
-                                   operations: 0 - logging disabled; 1 - log
-                                   statements other than SELECT and SHOW; 2 -
-                                   log all statements. Option takes precedence
-                                   over --log-sql in Dba.* context if enabled.
   --log-sql=off|error|on|all|unfiltered
                                    Log SQL statements: off - none of SQL
                                    statements will be logged; (default) error -
@@ -257,9 +253,6 @@ Usage: mysqlsh [OPTIONS] [URI]
   --js-debug-wait-attached         Waits for a debug client to get attached to
                                    a JavaScript debug server.
   --disable-plugins                Disable loading user plugins.
-  --dba=enableXProtocol            Enable the X protocol in the target server.
-                                   Requires a connection using classic session.
-                                   Deprecated.
   --quiet-start[={1|2}]            Avoids printing information when the shell
                                    is started. A value of 1 will prevent
                                    printing the shell version information. A
diff --git a/unittest/scripts/auto/js_shell/validation/options_help_norecord.js b/unittest/scripts/auto/js_shell/validation/options_help_norecord.js
index e77084ff30..0f8b093ba1 100644
--- a/unittest/scripts/auto/js_shell/validation/options_help_norecord.js
+++ b/unittest/scripts/auto/js_shell/validation/options_help_norecord.js
@@ -30,10 +30,6 @@ DESCRIPTION
         clusterset, or adding an instance to one
       - dba.gtidWaitTimeout: timeout value in seconds to wait for GTIDs to be
         synchronized
-      - dba.logSql: 0..2, log SQL statements executed by AdminAPI operations: 0
-        - logging disabled; 1 - log statements other than SELECT and SHOW; 2 -
-        log all statements. Option takes precedence over --log-sql in Dba.*
-        context, if enabled.
       - dba.restartWaitTimeout: timeout in seconds to wait for MySQL server to
         come back after a restart during clone recovery
       - dba.versionCompatibilityChecks: bool, checks version compatibility for
@@ -78,7 +74,6 @@ DESCRIPTION
         file
       - oci.profile: Specify which section in oci.configFile will be used as
         profile settings
-      - outputFormat: Deprecated, use resultFormat instead
       - pager: string which specifies the external command which is going to be
         used to display the paged output
       - passwordsFromStdin: boolean value that indicates if the shell should
diff --git a/unittest/scripts/auto/js_shell/validation/shell_help_norecord.js b/unittest/scripts/auto/js_shell/validation/shell_help_norecord.js
index 031b21be79..30c70b1e04 100644
--- a/unittest/scripts/auto/js_shell/validation/shell_help_norecord.js
+++ b/unittest/scripts/auto/js_shell/validation/shell_help_norecord.js
@@ -36,9 +36,15 @@ FUNCTIONS
       deleteAllCredentials()
             Deletes all credentials managed by the configured helper.
 
+      deleteAllSecrets()
+            Deletes all secrets managed by the configured helper.
+
       deleteCredential(url)
             Deletes credential for the given URL using the configured helper.
 
+      deleteSecret(key)
+            Deletes secret for the given key using the configured helper.
+
       disablePager()
             Disables pager for the current scripting mode.
 
@@ -65,6 +71,10 @@ FUNCTIONS
       listCredentials()
             Retrieves a list of all URLs stored by the configured helper.
 
+      listSecrets()
+            Retrieves a list of all secrets' keys stored by the configured
+            helper.
+
       listSqlHandlers()
             Lists the name and description of any registered SQL handlers.
 
@@ -83,6 +93,9 @@ FUNCTIONS
       prompt(message[, options])
             Utility function to prompt data from the user.
 
+      readSecret(key)
+            Reads secret for the given key using the configured helper.
+
       reconnect()
             Reconnect the global session.
 
@@ -108,6 +121,9 @@ FUNCTIONS
       storeCredential(url[, password])
             Stores given credential using the configured helper.
 
+      storeSecret(key[, value])
+            Stores given secret using the configured helper.
+
       unparseUri(options)
             Formats the given connection options to a URI string suitable for
             mysqlsh.
@@ -144,10 +160,6 @@ DESCRIPTION
         clusterset, or adding an instance to one
       - dba.gtidWaitTimeout: timeout value in seconds to wait for GTIDs to be
         synchronized
-      - dba.logSql: 0..2, log SQL statements executed by AdminAPI operations: 0
-        - logging disabled; 1 - log statements other than SELECT and SHOW; 2 -
-        log all statements. Option takes precedence over --log-sql in Dba.*
-        context, if enabled.
       - dba.restartWaitTimeout: timeout in seconds to wait for MySQL server to
         come back after a restart during clone recovery
       - dba.versionCompatibilityChecks: bool, checks version compatibility for
@@ -192,7 +204,6 @@ DESCRIPTION
         file
       - oci.profile: Specify which section in oci.configFile will be used as
         profile settings
-      - outputFormat: Deprecated, use resultFormat instead
       - pager: string which specifies the external command which is going to be
         used to display the paged output
       - passwordsFromStdin: boolean value that indicates if the shell should
@@ -651,6 +662,13 @@ EXCEPTIONS
       - if configured credential helper is invalid.
       - if deleting the credentials fails.
 
+//@<OUT> Help on deleteAllSecrets
+NAME
+      deleteAllSecrets - Deletes all secrets managed by the configured helper.
+
+SYNTAX
+      shell.deleteAllSecrets()
+
 //@<OUT> Help on deleteCredential
 NAME
       deleteCredential - Deletes credential for the given URL using the
@@ -673,6 +691,17 @@ EXCEPTIONS
       - if configured credential helper is invalid.
       - if deleting the credential fails.
 
+//@<OUT> Help on deleteSecret
+NAME
+      deleteSecret - Deletes secret for the given key using the configured
+                     helper.
+
+SYNTAX
+      shell.deleteSecret(key)
+
+WHERE
+      key: A key of the secret to delete.
+
 //@<OUT> Help on disablePager
 NAME
       disablePager - Disables pager for the current scripting mode.
@@ -750,7 +779,17 @@ DESCRIPTION
       The special values "default" and "<disabled>" are not on the list.
 
       Only values on this list (plus "default" and "<disabled>") can be used to
-      set the "credentialStore.helper" option.
+      set the "credentialStore.helper" Shell option.
+
+      Limitations:
+
+      - The login-path helper cannot store secrets longer than 78 characters.
+      - The login-path helper cannot store the following characters: '\0',
+        '0x03', '0x04', '\n', '\r', '0x0F', '0x11', '0x12', '0x13', '0x15',
+        '0x16', '0x17', '0x19', '0x1A', '0x1C', '0x7F'.
+      - The keychain helper cannot store the following characters: '\0',
+        '\n'.
+      - The secret-service helper can only store UTF-8 encoded strings.
 
 //@<OUT> Help on listCredentials
 NAME
@@ -769,6 +808,14 @@ EXCEPTIONS
       - if configured credential helper is invalid.
       - if listing the URLs fails.
 
+//@<OUT> Help on listSecrets
+NAME
+      listSecrets - Retrieves a list of all secrets' keys stored by the
+                    configured helper.
+
+SYNTAX
+      shell.listSecrets()
+
 //@<OUT> Help on listSshConnections
 NAME
       listSshConnections - Retrieves a list of all active SSH tunnels.
@@ -945,6 +992,19 @@ NAME
 SYNTAX
       shell.disconnect()
 
+//@<OUT> Help on readSecret
+NAME
+      readSecret - Reads secret for the given key using the configured helper.
+
+SYNTAX
+      shell.readSecret(key)
+
+WHERE
+      key: A key of the secret to read.
+
+RETURNS
+      Secret associated with the given key.
+
 //@<OUT> Help on reconnect
 NAME
       reconnect - Reconnect the global session.
@@ -1258,6 +1318,28 @@ EXCEPTIONS
       - if configured credential helper is invalid.
       - if storing the credential fails.
 
+//@<OUT> Help on storeSecret
+NAME
+      storeSecret - Stores given secret using the configured helper.
+
+SYNTAX
+      shell.storeSecret(key[, value])
+
+WHERE
+      key: A key that uniquely identifies the secret.
+      value: Value for the given key.
+
+DESCRIPTION
+      If value is not provided, displays a prompt to enter the secret.
+
+      If secret with the given key is already in the storage, its value is
+      overwritten.
+
+      The current helper is set by the credentialStore.helper Shell option.
+
+      The limitations of helpers on allowed characters do not apply to secrets
+      stored by storeSecret().
+
 //@<OUT> BUG28393119 UNABLE TO GET HELP ON CONNECTION DATA, before session
 Information about the data used to create sessions.
 
diff --git a/unittest/scripts/auto/js_shell/validation/shell_options_help_norecord.js b/unittest/scripts/auto/js_shell/validation/shell_options_help_norecord.js
index ac822b1782..6312f9f044 100644
--- a/unittest/scripts/auto/js_shell/validation/shell_options_help_norecord.js
+++ b/unittest/scripts/auto/js_shell/validation/shell_options_help_norecord.js
@@ -35,11 +35,6 @@
            and 8 or any of [none, internal, error, warning, info, debug,
            debug2, debug3] respectively.
 
-//@<OUT> outputFormat option help text
- outputFormat  outputFormat option was deprecated, please use resultFormat to
-               set result format and --json command line option to wrap output
-               in JSON instead.
-
 //@<OUT> resultFormat option help text
  resultFormat  Determines format of results. Allowed values: [table, tabbed,
                vertical, json, ndjson, json/raw, json/array, json/pretty].
@@ -60,13 +55,6 @@
  dba.gtidWaitTimeout  Timeout value in seconds to wait for GTIDs to be
                       synchronized.
 
-//@<OUT> dba.logSql option help text
- dba.logSql  Log SQL statements executed by AdminAPI operations: 0 - logging
-             disabled; 1 - log statements other than SELECT and SHOW; 2 - log
-             all statements. Option takes precedence over --log-sql in Dba.*
-             context if enabled.
-
-
 //@<OUT> Verify the help text when using filter
  history.autoSave           Automatically persist command line history when
                             exiting the Shell.
diff --git a/unittest/scripts/auto/js_shell/validation/shell_options_persists_norecord.js b/unittest/scripts/auto/js_shell/validation/shell_options_persists_norecord.js
index cb07db3f7b..1b8741ad41 100644
--- a/unittest/scripts/auto/js_shell/validation/shell_options_persists_norecord.js
+++ b/unittest/scripts/auto/js_shell/validation/shell_options_persists_norecord.js
@@ -63,13 +63,6 @@
 ||
 |5|
 
-//@ outputFormat update and set back to default using shell.options
-||
-|tabbed|
-|"outputFormat": "tabbed"|
-||
-|table|
-
 //@ resultFormat update and set back to default using shell.options
 ||
 |json/raw|
@@ -126,13 +119,6 @@
 ||
 |60|
 
-//@ dba.logSql update and set back to default using shell.options
-||
-|1|
-|"dba.logSql": "1"|
-||
-|0|
-
 //@ autocomplete.nameCache update and set back to default using \option
 ||
 |false|
@@ -190,12 +176,6 @@
 ||
 |5|
 
-//@ outputFormat update and set back to default using \option
-||
-|tabbed|
-||
-|table|
-
 //@ resultFormat update and set back to default using \option
 ||
 |json/raw|
@@ -250,13 +230,6 @@
 ||
 |60|
 
-//@ dba.logSql update and set back to default using \option
-||
-|2|
-|"dba.logSql": "2"|
-||
-|0|
-
 //@ credentialStore.helper update and set back to default using \option
 ||
 |plaintext|
@@ -309,7 +282,6 @@
  dba.connectTimeout              5
  dba.connectivityChecks          true
  dba.gtidWaitTimeout             60
- dba.logSql                      0
  dba.restartWaitTimeout          60
  dba.versionCompatibilityChecks  true
  defaultCompress                 false
@@ -328,7 +300,6 @@
  mysqlPluginDir                  [[*]]plugins
  oci.configFile                  <<<_defaultOciConfigFile>>>
  oci.profile                     DEFAULT
- outputFormat                    table
  pager                           ""
  passwordsFromStdin              false
  resultFormat                    table
@@ -350,7 +321,6 @@
  dba.connectTimeout              5 (Compiled default)
  dba.connectivityChecks          true (Compiled default)
  dba.gtidWaitTimeout             60 (Compiled default)
- dba.logSql                      0 (Compiled default)
  dba.restartWaitTimeout          60 (Compiled default)
  dba.versionCompatibilityChecks  true (Compiled default)
  defaultCompress                 false (Compiled default)
@@ -369,7 +339,6 @@
  mysqlPluginDir                  [[*]]plugins (Compiled default)
  oci.configFile                  <<<_defaultOciConfigFile>>> (Compiled default)
  oci.profile                     DEFAULT (Compiled default)
- outputFormat                    table (Compiled default)
  pager                           "" (Compiled default)
  passwordsFromStdin              false (Compiled default)
  resultFormat                    table (Compiled default)
@@ -412,15 +381,6 @@
 ||
 ||
 
-//@ Verify option dba.logSql
-||Malformed option value.
-||value out of range
-||value out of range
-||Incorrect option value.
-||
-||
-||
-
 //@ Verify option verbose
 ||Malformed option value.
 ||value out of range
@@ -472,7 +432,6 @@
  dba.connectTimeout              5
  dba.connectivityChecks          true
  dba.gtidWaitTimeout             60
- dba.logSql                      0
  dba.restartWaitTimeout          60
  dba.versionCompatibilityChecks  true
  defaultCompress                 false
@@ -491,7 +450,6 @@
  mysqlPluginDir                  [[*]]plugins
  oci.configFile                  <<<_defaultOciConfigFile>>>
  oci.profile                     DEFAULT
- outputFormat                    table
  pager                           ""
  passwordsFromStdin              false
  resultFormat                    table
@@ -514,7 +472,6 @@ Switching to SQL mode... Commands end with ;
  dba.connectTimeout              5 (Compiled default)
  dba.connectivityChecks          true (Compiled default)
  dba.gtidWaitTimeout             60 (Compiled default)
- dba.logSql                      0 (Compiled default)
  dba.restartWaitTimeout          60 (Compiled default)
  dba.versionCompatibilityChecks  true (Compiled default)
  defaultCompress                 false (Compiled default)
@@ -533,7 +490,6 @@ Switching to SQL mode... Commands end with ;
  mysqlPluginDir                  [[*]]plugins (Compiled default)
  oci.configFile                  <<<_defaultOciConfigFile>>> (Compiled default)
  oci.profile                     DEFAULT (Compiled default)
- outputFormat                    table (Compiled default)
  pager                           "" (Compiled default)
  passwordsFromStdin              false (Compiled default)
  resultFormat                    table (Compiled default)
diff --git a/unittest/scripts/auto/js_shell/validation/util_help_norecord.js b/unittest/scripts/auto/js_shell/validation/util_help_norecord.js
index 91a7fd6cb4..14e8f3021a 100644
--- a/unittest/scripts/auto/js_shell/validation/util_help_norecord.js
+++ b/unittest/scripts/auto/js_shell/validation/util_help_norecord.js
@@ -10,6 +10,11 @@ DESCRIPTION
 PROPERTIES
       debug
             Debugging and diagnostic utilities.
+?{__has_util_plugin}
+
+      visual
+            Visualization utilities.
+?{}
 
 FUNCTIONS
       checkForServerUpgrade([connectionData][, options])
@@ -642,8 +647,8 @@ DESCRIPTION
         instead of the default one.
       - ociAuth: string (default: not set) - Use the specified authentication
         method when connecting to the OCI. Allowed values: api_key (used when
-        not explicitly set), instance_principal, resource_principal,
-        security_token.
+        not explicitly set), instance_obo_user, instance_principal,
+        resource_principal, security_token.
       - s3BucketName: string (default: not set) - Name of the AWS S3 bucket to
         use. The bucket must already exist.
       - s3CredentialsFile: string (default: not set) - Use the specified AWS
@@ -857,8 +862,8 @@ DESCRIPTION
         instead of the default one.
       - ociAuth: string (default: not set) - Use the specified authentication
         method when connecting to the OCI. Allowed values: api_key (used when
-        not explicitly set), instance_principal, resource_principal,
-        security_token.
+        not explicitly set), instance_obo_user, instance_principal,
+        resource_principal, security_token.
       - s3BucketName: string (default: not set) - Name of the AWS S3 bucket to
         use. The bucket must already exist.
       - s3CredentialsFile: string (default: not set) - Use the specified AWS
@@ -1270,8 +1275,8 @@ DESCRIPTION
         instead of the default one.
       - ociAuth: string (default: not set) - Use the specified authentication
         method when connecting to the OCI. Allowed values: api_key (used when
-        not explicitly set), instance_principal, resource_principal,
-        security_token.
+        not explicitly set), instance_obo_user, instance_principal,
+        resource_principal, security_token.
       - s3BucketName: string (default: not set) - Name of the AWS S3 bucket to
         use. The bucket must already exist.
       - s3CredentialsFile: string (default: not set) - Use the specified AWS
@@ -1654,8 +1659,8 @@ DESCRIPTION
         instead of the default one.
       - ociAuth: string (default: not set) - Use the specified authentication
         method when connecting to the OCI. Allowed values: api_key (used when
-        not explicitly set), instance_principal, resource_principal,
-        security_token.
+        not explicitly set), instance_obo_user, instance_principal,
+        resource_principal, security_token.
       - s3BucketName: string (default: not set) - Name of the AWS S3 bucket to
         use. The bucket must already exist.
       - s3CredentialsFile: string (default: not set) - Use the specified AWS
@@ -1994,8 +1999,8 @@ DESCRIPTION
         instead of the default one.
       - ociAuth: string (default: not set) - Use the specified authentication
         method when connecting to the OCI. Allowed values: api_key (used when
-        not explicitly set), instance_principal, resource_principal,
-        security_token.
+        not explicitly set), instance_obo_user, instance_principal,
+        resource_principal, security_token.
       - s3BucketName: string (default: not set) - Name of the AWS S3 bucket to
         use. The bucket must already exist.
       - s3CredentialsFile: string (default: not set) - Use the specified AWS
@@ -2257,8 +2262,8 @@ DESCRIPTION
         instead of the default one.
       - ociAuth: string (default: not set) - Use the specified authentication
         method when connecting to the OCI. Allowed values: api_key (used when
-        not explicitly set), instance_principal, resource_principal,
-        security_token.
+        not explicitly set), instance_obo_user, instance_principal,
+        resource_principal, security_token.
       - s3BucketName: string (default: not set) - Name of the AWS S3 bucket to
         use. The bucket must already exist.
       - s3CredentialsFile: string (default: not set) - Use the specified AWS
@@ -2378,8 +2383,8 @@ DESCRIPTION
         instead of the default one.
       - ociAuth: string (default: not set) - Use the specified authentication
         method when connecting to the OCI. Allowed values: api_key (used when
-        not explicitly set), instance_principal, resource_principal,
-        security_token.
+        not explicitly set), instance_obo_user, instance_principal,
+        resource_principal, security_token.
       - s3BucketName: string (default: not set) - Name of the AWS S3 bucket to
         use. The bucket must already exist.
       - s3CredentialsFile: string (default: not set) - Use the specified AWS
@@ -2638,8 +2643,8 @@ DESCRIPTION
         instead of the default one.
       - ociAuth: string (default: not set) - Use the specified authentication
         method when connecting to the OCI. Allowed values: api_key (used when
-        not explicitly set), instance_principal, resource_principal,
-        security_token.
+        not explicitly set), instance_obo_user, instance_principal,
+        resource_principal, security_token.
       - s3BucketName: string (default: not set) - Name of the AWS S3 bucket to
         use. The bucket must already exist.
       - s3CredentialsFile: string (default: not set) - Use the specified AWS
@@ -2723,7 +2728,8 @@ Oracle Cloud Infrastructure (OCI) Object Storage Options
   of the default one.
 - ociAuth: string (default: not set) - Use the specified authentication method
   when connecting to the OCI. Allowed values: api_key (used when not explicitly
-  set), instance_principal, resource_principal, security_token.
+  set), instance_obo_user, instance_principal, resource_principal,
+  security_token.
 
 Description
 
@@ -2741,6 +2747,7 @@ The ociAuth option allows to specify the authentication method used when
 connecting to the OCI:
 
 - api_key - API Key-Based Authentication
+- instance_obo_user - Instance Principal Authentication with delegation token
 - instance_principal - Instance Principal Authentication
 - resource_principal - Resource Principal Authentication
 - security_token - Session Token-Based Authentication
diff --git a/unittest/scripts/auto/py_adminapi/scripts/cluster.py b/unittest/scripts/auto/py_adminapi/scripts/cluster.py
index 9e7df46d65..9e63f476f8 100644
--- a/unittest/scripts/auto/py_adminapi/scripts/cluster.py
+++ b/unittest/scripts/auto/py_adminapi/scripts/cluster.py
@@ -38,7 +38,6 @@
     'fence_writes',
     'unfence_writes',
     'add_replica_instance',
-    'routing_options',
     'set_routing_option',
     'router_options',
     'create_routing_guideline',
diff --git a/unittest/scripts/auto/py_adminapi_clusterset/scripts/transaction_consistency_norecord.py b/unittest/scripts/auto/py_adminapi_clusterset/scripts/transaction_consistency_norecord.py
index 96a83b94a7..8b9093f9a2 100644
--- a/unittest/scripts/auto/py_adminapi_clusterset/scripts/transaction_consistency_norecord.py
+++ b/unittest/scripts/auto/py_adminapi_clusterset/scripts/transaction_consistency_norecord.py
@@ -57,7 +57,7 @@
 session2.run_sql("start replica for channel 'clusterset_replication'")
 
 #shell.options["verbose"]=1
-#shell.options["dba.logSql"]=2
+#shell.options["logSql"]="unfiltered"
 
 shell.dump_rows(session2.run_sql("select @@gtid_executed, @@group_replication_group_name, @@group_replication_view_change_uuid"), "vertical")
 
diff --git a/unittest/scripts/auto/py_aws/scripts/util_dump_and_load_aws_norecord.py b/unittest/scripts/auto/py_aws/scripts/util_dump_and_load_aws_norecord.py
index c74e29998a..934299f5fd 100644
--- a/unittest/scripts/auto/py_aws/scripts/util_dump_and_load_aws_norecord.py
+++ b/unittest/scripts/auto/py_aws/scripts/util_dump_and_load_aws_norecord.py
@@ -170,7 +170,7 @@ def log_message(self, format, *args):
 #@<> WL14387-TSFR_4_1_1
 with write_profile(default_aws_config_file, "profile " + local_aws_profile, { "aws_access_key_id": "invalid", "aws_secret_access_key": "invalid" }):
     with write_profile(default_aws_credentials_file, local_aws_profile, {}):
-        with write_profile(local_aws_config_file, local_aws_profile, aws_settings):
+        with write_profile(local_aws_config_file, "profile " + local_aws_profile, aws_settings):
             # empty strings cause dumper/loader to use files from the default location
             EXPECT_SUCCESS({ "s3Profile": local_aws_profile, "s3ConfigFile": local_aws_config_file, "s3CredentialsFile": "" })
 
diff --git a/unittest/scripts/auto/py_aws/scripts/util_export_and_import_aws_norecord.py b/unittest/scripts/auto/py_aws/scripts/util_export_and_import_aws_norecord.py
index 9a7f71d6b2..5e83256320 100644
--- a/unittest/scripts/auto/py_aws/scripts/util_export_and_import_aws_norecord.py
+++ b/unittest/scripts/auto/py_aws/scripts/util_export_and_import_aws_norecord.py
@@ -150,7 +150,7 @@ def load():
 #@<> WL14387-TSFR_4_1_1
 with write_profile(default_aws_config_file, "profile " + local_aws_profile, { "aws_access_key_id": "invalid", "aws_secret_access_key": "invalid" }):
     with write_profile(default_aws_credentials_file, local_aws_profile, {}):
-        with write_profile(local_aws_config_file, local_aws_profile, aws_settings):
+        with write_profile(local_aws_config_file, "profile " + local_aws_profile, aws_settings):
             # empty strings cause dumper/loader to use files from the default location
             EXPECT_SUCCESS({ "s3Profile": local_aws_profile, "s3ConfigFile": local_aws_config_file, "s3CredentialsFile": "" })
 
diff --git a/unittest/scripts/auto/py_devapi/validation/mysqlx_help_norecord.py b/unittest/scripts/auto/py_devapi/validation/mysqlx_help_norecord.py
index 80d1a256f4..8f1e21cdfd 100644
--- a/unittest/scripts/auto/py_devapi/validation/mysqlx_help_norecord.py
+++ b/unittest/scripts/auto/py_devapi/validation/mysqlx_help_norecord.py
@@ -868,6 +868,9 @@
       drop_schema(name)
             Drops the schema with the specified name.
 
+      get_client_data(key)
+            Returns value associated with the session for the given key.
+
       get_connection_id()
             Retrieves the connection id for the current session.
 
@@ -884,6 +887,9 @@
       get_schemas()
             Retrieves the Schemas available on the session.
 
+      get_sql_mode()
+            Retrieves the SQL_MODE for the current session.
+
       get_ssh_uri()
             Retrieves the SSH URI for the current session.
 
@@ -913,6 +919,9 @@
       run_sql(query[, args])
             Executes a query and returns the corresponding SqlResult object.
 
+      set_client_data(key, value)
+            Associates a value with the session for the given key.
+
       set_current_schema(name)
             Sets the current schema for this session, and returns the schema
             object for it.
diff --git a/unittest/scripts/auto/py_devapi/validation/mysqlx_session_help.py b/unittest/scripts/auto/py_devapi/validation/mysqlx_session_help.py
index 51968fe7ea..5beb60627b 100644
--- a/unittest/scripts/auto/py_devapi/validation/mysqlx_session_help.py
+++ b/unittest/scripts/auto/py_devapi/validation/mysqlx_session_help.py
@@ -48,6 +48,9 @@
       drop_schema(name)
             Drops the schema with the specified name.
 
+      get_client_data(key)
+            Returns value associated with the session for the given key.
+
       get_connection_id()
             Retrieves the connection id for the current session.
 
@@ -64,6 +67,9 @@
       get_schemas()
             Retrieves the Schemas available on the session.
 
+      get_sql_mode()
+            Retrieves the SQL_MODE for the current session.
+
       get_ssh_uri()
             Retrieves the SSH URI for the current session.
 
@@ -93,6 +99,9 @@
       run_sql(query[, args])
             Executes a query and returns the corresponding SqlResult object.
 
+      set_client_data(key, value)
+            Associates a value with the session for the given key.
+
       set_current_schema(name)
             Sets the current schema for this session, and returns the schema
             object for it.
diff --git a/unittest/scripts/auto/py_mixed_versions/scripts/transaction_consistency_mixed_versions_norecord.py b/unittest/scripts/auto/py_mixed_versions/scripts/transaction_consistency_mixed_versions_norecord.py
index f5b4febae3..ab5f3bdca0 100644
--- a/unittest/scripts/auto/py_mixed_versions/scripts/transaction_consistency_mixed_versions_norecord.py
+++ b/unittest/scripts/auto/py_mixed_versions/scripts/transaction_consistency_mixed_versions_norecord.py
@@ -73,7 +73,7 @@
 session2.run_sql("start replica for channel 'clusterset_replication'")
 
 #shell.options["verbose"]=1
-#shell.options["dba.logSql"]=2
+#shell.options["logSql"]="unfiltered"
 
 shell.dump_rows(session2.run_sql("select @@gtid_executed, @@group_replication_group_name, @@group_replication_view_change_uuid"), "vertical")
 
diff --git a/unittest/scripts/auto/py_shell/scripts/_ssh_utils.py b/unittest/scripts/auto/py_shell/scripts/_ssh_utils.py
index 5faa01202a..30af8f80ca 100644
--- a/unittest/scripts/auto/py_shell/scripts/_ssh_utils.py
+++ b/unittest/scripts/auto/py_shell/scripts/_ssh_utils.py
@@ -1,9 +1,13 @@
 import os
-from mysqlsh import globals
 
+g = globals
+
+from mysqlsh import globals
 shell_ = globals.shell
 testutil_ = globals.testutil
 
+globals = g
+
 # Reads the configuration from the env vars
 SSH_URI = os.environ["SSH_URI"]
 
diff --git a/unittest/scripts/auto/py_shell/scripts/local_infile_norecord.py b/unittest/scripts/auto/py_shell/scripts/local_infile_norecord.py
new file mode 100644
index 0000000000..110a5e4b75
--- /dev/null
+++ b/unittest/scripts/auto/py_shell/scripts/local_infile_norecord.py
@@ -0,0 +1,50 @@
+#@<> entry point
+## imports
+import os
+import os.path
+
+## constants
+schema_name = "local-schema"
+table_name = "local-table"
+file_name = os.path.join(__tmp_dir, "local-infile.tsv")
+
+#@<> setup
+shell.connect(__mysqluripwd)
+session.run_sql("DROP SCHEMA IF EXISTS !", [schema_name])
+session.run_sql("CREATE SCHEMA !", [schema_name])
+session.run_sql("CREATE TABLE !.! (id INT PRIMARY KEY, data TEXT)", [schema_name, table_name])
+
+old_local_infile = session.run_sql("SELECT @@GLOBAL.local_infile").fetch_one()[0]
+session.run_sql("SET @@GLOBAL.local_infile = ON")
+
+session.close()
+
+with open(file_name, "wb") as f:
+    f.write(bytes("1\tQWERTY\n", "utf-8"))
+    f.write(bytes("2\tasdfg\n", "utf-8"))
+    f.write(bytes("3\tZxCvB\n", "utf-8"))
+
+#@<> X session does not support local-infile
+for value in ["0", "false", "1", "true"]:
+    EXPECT_THROWS(lambda: shell.connect(__uripwd + "?local-infile=" + value), "RuntimeError: X Protocol: LOAD DATA LOCAL INFILE is not supported.")
+
+#@<> classic session with local-infile disabled
+for value in ["0", "false"]:
+    EXPECT_NO_THROWS(lambda: shell.connect(__mysqluripwd + "?local-infile=" + value), "Classic session should be established.")
+    EXPECT_THROWS(lambda: session.run_sql("LOAD DATA LOCAL INFILE ? INTO TABLE !.!", [file_name, schema_name, table_name]), "DBError: MySQL Error (2068): LOAD DATA LOCAL INFILE file request rejected due to restrictions on access.")
+    EXPECT_NO_THROWS(lambda: shell.disconnect(), "Classic session should be closed.")
+
+#@<> classic session with local-infile enabled
+for value in ["1", "true"]:
+    EXPECT_NO_THROWS(lambda: shell.connect(__mysqluripwd + "?local-infile=" + value), "Classic session should be established.")
+    EXPECT_NO_THROWS(lambda: session.run_sql("LOAD DATA LOCAL INFILE ? INTO TABLE !.!", [file_name, schema_name, table_name]), "Load should succeed.")
+    EXPECT_NO_THROWS(lambda: session.run_sql("TRUNCATE TABLE !.!", [schema_name, table_name]), "Truncate should succeed.")
+    EXPECT_NO_THROWS(lambda: shell.disconnect(), "Classic session should be closed.")
+
+#@<> cleanup
+shell.connect(__mysqluripwd)
+session.run_sql("DROP SCHEMA IF EXISTS !", [schema_name])
+session.run_sql("SET @@GLOBAL.local_infile = ?", [old_local_infile])
+session.close()
+
+os.remove(file_name)
diff --git a/unittest/scripts/auto/py_shell/scripts/mysql_classic_session_help_norecord.py b/unittest/scripts/auto/py_shell/scripts/mysql_classic_session_help_norecord.py
index 9f30df3963..ebf47bc9dd 100644
--- a/unittest/scripts/auto/py_shell/scripts/mysql_classic_session_help_norecord.py
+++ b/unittest/scripts/auto/py_shell/scripts/mysql_classic_session_help_norecord.py
@@ -96,3 +96,12 @@
 
 #@ Help on set_query_attributes, \? [USE:Help on set_query_attributes]
 \? classicsession.set_query_attributes
+
+#@ Help on get_sql_mode()
+\? ClassicSession.get_sql_mode
+
+#@ Help on set_client_data()
+\? ClassicSession.set_client_data
+
+#@ Help on get_client_data()
+\? ClassicSession.get_client_data
diff --git a/unittest/scripts/auto/py_shell/scripts/plugin_decorator_norecord.py b/unittest/scripts/auto/py_shell/scripts/plugin_decorator_norecord.py
index 59ee53639b..4860b7d642 100644
--- a/unittest/scripts/auto/py_shell/scripts/plugin_decorator_norecord.py
+++ b/unittest/scripts/auto/py_shell/scripts/plugin_decorator_norecord.py
@@ -664,6 +664,22 @@ def listdata2(four, five, six):
 
     Lists all users of a given compartment.
 
+    Args:
+        four (object): An OCI config object or None.
+        five (bool): If set to false exceptions are raised
+        six (bool): If set to true, a list object is returned.
+
+    Returns:
+        A list of users
+    """
+    pass
+
+@plugin_function('plugin2.list.webData', shell=False, cli=False, web=True)
+def listdata3(four, five, six):
+    """Lists users
+
+    Lists all users of a given compartment.
+
     Args:
         four (object): An OCI config object or None.
         five (bool): If set to false exceptions are raised
@@ -685,6 +701,26 @@ def listdata2(four, five, six):
 #@ plugin2.list.data
 rc = call_mysqlsh_py_e("\\? plugin2.list.data")
 
+#@<> plugin2.list.webData
+rc = call_mysqlsh_py_e("\\? plugin2.list.webData")
+EXPECT_STDOUT_CONTAINS("No help items found matching 'plugin2.list.webData'")
+
+
+#@<> Ensure web only plugin is available
+web_plugins_code = """
+from mysqlsh.plugin_manager import registrar
+
+for definition in registrar.get_registry():
+    if definition.web and not definition.shell:
+        print(f"Found web only plugin: {definition.fully_qualified_name}")
+"""
+
+testutil.create_file("web_plugins.py", web_plugins_code)
+testutil.call_mysqlsh(["--quiet-start=2", "--py", "-f", "web_plugins.py"])
+EXPECT_STDOUT_CONTAINS("Found web only plugin: plugin2.list.webData")
+
+
 #@<> Finalization
 testutil.rmdir(plugins_path, True)
 testutil.rmfile("my-script.py")
+testutil.rmfile("web_plugins.py")
diff --git a/unittest/scripts/auto/py_shell/scripts/session_misc_norecord.py b/unittest/scripts/auto/py_shell/scripts/session_misc_norecord.py
new file mode 100644
index 0000000000..e0f1784db5
--- /dev/null
+++ b/unittest/scripts/auto/py_shell/scripts/session_misc_norecord.py
@@ -0,0 +1,81 @@
+#@<> setup
+shell.connect(__mysqluripwd)
+
+def com_select(sess):
+    return int(sess.run_sql("show session status like 'Com_select'").fetch_one()[1])
+
+#@<> get_sql_mode without tracking
+session2 = shell.open_session(__mysqluripwd)
+
+real_sql_mode = session2.run_sql("select @@sql_mode").fetch_one()[0]
+EXPECT_EQ(real_sql_mode, session2.get_sql_mode())
+
+#@<> sql_mode and tracking in classic session {VER(>=8.0)}
+
+# session tracking is enabled by default on global session
+session.run_sql("set sql_mode='ansi_quotes'")
+EXPECT_EQ("ANSI_QUOTES", session.get_sql_mode())
+
+#@<> sql_mode tracking in a new session {VER(>=8.0)}
+EXPECT_EQ(real_sql_mode, session2.get_sql_mode())
+
+# session tracking disabled by default in non-global sessions - in that case, it should always query sql_mode
+num_selects = com_select(session2)
+session2.run_sql("set sql_mode='ansi_quotes'")
+EXPECT_EQ("ANSI_QUOTES", session2.get_sql_mode())
+EXPECT_EQ("ANSI_QUOTES", session2.get_sql_mode())
+EXPECT_EQ(2, com_select(session2) - num_selects)
+
+tracked_vars = session2.track_system_variable("sql_mode")
+EXPECT_TRUE("sql_mode" in tracked_vars)
+
+# session tracking now enabled - no queries on sql_mode at all
+num_selects = com_select(session2)
+session2.run_sql("set sql_mode='ansi_quotes'")
+EXPECT_EQ("ANSI_QUOTES", session2.get_sql_mode())
+EXPECT_EQ("ANSI_QUOTES", session2.get_sql_mode())
+
+session2.run_sql("set sql_mode=''")
+EXPECT_EQ("", session2.get_sql_mode())
+EXPECT_EQ("", session2.get_sql_mode())
+EXPECT_EQ(0, com_select(session2) - num_selects)
+
+#@<> client data
+deleted = False
+
+
+class Data:
+    def __del__(self):
+        global deleted
+        deleted = True
+
+
+session.set_client_data("mydata", {"HELLO": "WORLD"})
+session.set_client_data("null", None)
+session.set_client_data("binary", b"binarydata")
+data = Data()
+session.set_client_data("opaque", data)
+
+EXPECT_EQ({"HELLO": "WORLD"}, session.get_client_data("mydata"))
+EXPECT_EQ(None, session.get_client_data("null"))
+EXPECT_EQ(data, session.get_client_data("opaque"))
+EXPECT_EQ(b"binarydata", session.get_client_data("binary"))
+
+EXPECT_EQ(None, session.get_client_data("invalid"))
+
+del data
+
+import gc
+
+gc.collect()
+EXPECT_FALSE(deleted)
+
+session.set_client_data("opaque", None)
+EXPECT_EQ(None, session.get_client_data("opaque"))
+
+gc.collect()
+EXPECT_TRUE(deleted)
+
+#@<> cleanup
+session2.close()
+shell.disconnect()
diff --git a/unittest/scripts/auto/py_shell/scripts/shell_help_norecord.py b/unittest/scripts/auto/py_shell/scripts/shell_help_norecord.py
index 666bd5641a..f863f011f0 100644
--- a/unittest/scripts/auto/py_shell/scripts/shell_help_norecord.py
+++ b/unittest/scripts/auto/py_shell/scripts/shell_help_norecord.py
@@ -72,6 +72,15 @@
 #@ global help for delete_all_credentials [USE:shell.delete_all_credentials]
 \help Shell.delete_all_credentials
 
+#@ shell.delete_all_secrets
+shell.help('delete_all_secrets')
+
+#@ global ? for delete_all_secrets [USE: shell.delete_all_secrets]
+\? Shell.delete_all_secrets
+
+#@ global help for delete_all_secrets [USE: shell.delete_all_secrets]
+\help Shell.delete_all_secrets
+
 #@ shell.delete_credential
 shell.help('delete_credential')
 
@@ -81,6 +90,15 @@
 #@ global help for delete_credential [USE:shell.delete_credential]
 \help Shell.delete_credential
 
+#@ shell.delete_secret
+shell.help('delete_secret')
+
+#@ global ? for delete_secret [USE: shell.delete_secret]
+\? Shell.delete_secret
+
+#@ global help for delete_secret [USE: shell.delete_secret]
+\help Shell.delete_secret
+
 #@ shell.disable_pager
 shell.help('disable_pager')
 
@@ -135,6 +153,15 @@
 #@ global help for list_credentials [USE:shell.list_credentials]
 \help Shell.list_credentials
 
+#@ shell.list_secrets
+shell.help('list_secrets')
+
+#@ global ? for list_secrets [USE: shell.list_secrets]
+\? Shell.list_secrets
+
+#@ global help for list_secrets [USE: shell.list_secrets]
+\help Shell.list_secrets
+
 #@ shell.list_ssh_connections
 shell.help('list_ssh_connections')
 
@@ -189,6 +216,15 @@
 #@ global help for disconnect[USE:shell.disconnect]
 \help Shell.disconnect
 
+#@ shell.read_secret
+shell.help('read_secret')
+
+#@ global ? for read_secret [USE: shell.read_secret]
+\? Shell.read_secret
+
+#@ global help for read_secret [USE: shell.read_secret]
+\help Shell.read_secret
+
 #@ shell.reconnect
 shell.help('reconnect')
 
@@ -284,6 +320,15 @@
 #@ global help for store_credential [USE:shell.store_credential]
 \help Shell.store_credential
 
+#@ shell.store_secret
+shell.help('store_secret')
+
+#@ global ? for store_secret [USE: shell.store_secret]
+\? Shell.store_secret
+
+#@ global help for store_secret [USE: shell.store_secret]
+\help Shell.store_secret
+
 #@ shell.create_context
 shell.help('create_context')
 
diff --git a/unittest/scripts/auto/py_shell/scripts/shell_register_sql_handler_norecord.py b/unittest/scripts/auto/py_shell/scripts/shell_register_sql_handler_norecord.py
index 02fac069e7..e5823e0459 100644
--- a/unittest/scripts/auto/py_shell/scripts/shell_register_sql_handler_norecord.py
+++ b/unittest/scripts/auto/py_shell/scripts/shell_register_sql_handler_norecord.py
@@ -288,5 +288,39 @@ def my_handler(session, sql):
 EXPECT_STDOUT_NOT_CONTAINS('====> SQL HANDLER:')
 
 
-# @<> Finalization
+#@<> BUG#37196079 - SQL handler callbacks received SQL query still containing the placeholders
+plugin_code = """
+def my_handler(session, sql):
+    print("====> SQL HANDLER:", sql)
+
+shell.register_sql_handler("placeholdersHandler", "Handler with placeholders", ["SELECT "], my_handler)
+"""
+testutil.create_file(plugin_path, plugin_code)
+
+call_mysqlsh([__mysqluripwd, "--py", "-e", "session.run_sql('select 1', [])"])
+EXPECT_STDOUT_CONTAINS("""
+====> SQL HANDLER: select 1
+""")
+WIPE_OUTPUT()
+
+call_mysqlsh([__mysqluripwd, "--py", "-e", "session.run_sql('select ? from dual', [1])"])
+EXPECT_STDOUT_CONTAINS("""
+====> SQL HANDLER: select 1 from dual
+""")
+WIPE_OUTPUT()
+
+call_mysqlsh([__mysqluripwd, "--py", "-e", "session.run_sql('select ?,?, ? from DUAL', ['a', 2, 'c'])"])
+EXPECT_STDOUT_CONTAINS("""
+====> SQL HANDLER: select 'a',2, 'c' from DUAL
+""")
+WIPE_OUTPUT()
+
+call_mysqlsh([__mysqluripwd, "--py", "-e", "session.run_sql('select ? from dual', [])"])
+EXPECT_STDOUT_CONTAINS("""
+ValueError: Insufficient number of values for placeholders in query
+""")
+WIPE_OUTPUT()
+
+
+#@<> Finalization
 testutil.rmdir(plugins_path, True)
diff --git a/unittest/scripts/auto/py_shell/scripts/shell_secrets_norecord.py b/unittest/scripts/auto/py_shell/scripts/shell_secrets_norecord.py
new file mode 100644
index 0000000000..7ba07cf38c
--- /dev/null
+++ b/unittest/scripts/auto/py_shell/scripts/shell_secrets_norecord.py
@@ -0,0 +1,209 @@
+#@<> entry point
+# imports
+from contextlib import ExitStack
+
+# constants
+current_helper = shell.options["credentialStore.helper"]
+has_login_path = "login-path" in shell.list_credential_helpers()
+used_helpers = ["plaintext", "login-path"]
+
+test_key = "my-key"
+test_secret = "my-secret"
+actual_secret = ""
+actual_all_secrets = []
+
+# helpers
+def set_helper(helper):
+    shell.options["credentialStore.helper"] = helper
+
+def wipe_all_secrets():
+    for helper in shell.list_credential_helpers():
+        if helper in used_helpers:
+            print("--> removing all secrets from:", helper)
+            set_helper(helper)
+            shell.delete_all_credentials()
+            shell.delete_all_secrets()
+
+def read_secret(key):
+    global actual_secret
+    actual_secret = shell.read_secret(key)
+
+def list_all_secrets():
+    global actual_all_secrets
+    actual_all_secrets = shell.list_secrets()
+
+def TEST(helper):
+    global actual_secret
+    actual_secret = None
+    global actual_all_secrets
+    actual_all_secrets = None
+    EXPECT_NO_THROWS(lambda: set_helper(helper))
+    stack = ExitStack()
+    stack.callback(lambda: EXPECT_NO_THROWS(lambda: wipe_all_secrets()))
+    return stack
+
+#@<> Setup
+wipe_all_secrets()
+
+#@<> WL16958-TSFR_1_2_1 - valid key
+with TEST("plaintext"):
+    # store
+    EXPECT_NO_THROWS(lambda: shell.store_secret(test_key, test_secret))
+    # read
+    EXPECT_NO_THROWS(lambda: read_secret(test_key))
+    # test
+    EXPECT_EQ(test_secret, actual_secret)
+    EXPECT_EQ("str", type(actual_secret).__name__)
+
+#@<> WL16958-TSFR_1_2_2 - login-path limitations {has_login_path}
+with TEST("login-path"):
+    EXPECT_THROWS(lambda: shell.store_secret(test_key, "x" * 100), "RuntimeError: Failed to save the secret: The login-path helper cannot store secrets longer than 78 characters.")
+
+#@<> WL16958-TSFR_1_2_8 - there are no limitations on secret's value {has_login_path}
+with TEST("login-path"):
+    # store
+    EXPECT_NO_THROWS(lambda: shell.store_secret(test_key, "\r\n"))
+    # read
+    EXPECT_NO_THROWS(lambda: read_secret(test_key))
+    # test
+    EXPECT_EQ("\r\n", actual_secret)
+
+#@<> WL16958-TSFR_1_2_3 - key - invalid type
+with TEST("plaintext"):
+    EXPECT_THROWS(lambda: shell.store_secret({}, test_secret), "TypeError: Argument #1 is expected to be a string")
+
+#@<> WL16958-TSFR_1_2_4 - value - invalid type
+with TEST("plaintext"):
+    EXPECT_THROWS(lambda: shell.store_secret(test_key, {}), "TypeError: Argument #2 is expected to be a string")
+
+#@<> WL16958-TSFR_1_2_5 - empty key
+with TEST("plaintext"):
+    EXPECT_THROWS(lambda: shell.store_secret("", test_secret), "ValueError: Key cannot be empty.")
+
+#@<> WL16958-TSFR_1_2_6 - prompt for empty value
+with TEST("plaintext"):
+    # store
+    testutil.expect_password("Please provide the secret to store: ", "")
+    EXPECT_NO_THROWS(lambda: shell.store_secret(test_key))
+    # read
+    EXPECT_NO_THROWS(lambda: read_secret(test_key))
+    # test
+    EXPECT_EQ("", actual_secret)
+
+#@<> WL16958-TSFR_1_2_7 - prompt for non-empty value
+with TEST("plaintext"):
+    # store
+    testutil.expect_password("Please provide the secret to store: ", test_secret)
+    EXPECT_NO_THROWS(lambda: shell.store_secret(test_key))
+    # read
+    EXPECT_NO_THROWS(lambda: read_secret(test_key))
+    # test
+    EXPECT_EQ(test_secret, actual_secret)
+
+#@<> WL16958-TSFR_1_3_1 - overwrite secret
+with TEST("plaintext"):
+    # store then overwrite
+    EXPECT_NO_THROWS(lambda: shell.store_secret(test_key, test_secret))
+    EXPECT_NO_THROWS(lambda: shell.store_secret(test_key, 2 * test_secret))
+    # read
+    EXPECT_NO_THROWS(lambda: read_secret(test_key))
+    # test
+    EXPECT_EQ(2 * test_secret, actual_secret)
+
+#@<> WL16958-TSFR_2_1_1 - invalid key
+with TEST("plaintext"):
+    EXPECT_THROWS(lambda: shell.read_secret({}), "TypeError: Argument #1 is expected to be a string")
+    EXPECT_THROWS(lambda: shell.read_secret(""), "ValueError: Key cannot be empty.")
+
+#@<> WL16958-TSFR_2_2_1 - unknown key
+with TEST("plaintext"):
+    EXPECT_THROWS(lambda: shell.read_secret(test_key), "RuntimeError: Failed to read the secret: Could not find the secret")
+
+#@<> WL16958-TSFR_3_1_1 - invalid key
+with TEST("plaintext"):
+    EXPECT_THROWS(lambda: shell.delete_secret({}), "TypeError: Argument #1 is expected to be a string")
+    EXPECT_THROWS(lambda: shell.delete_secret(""), "ValueError: Key cannot be empty.")
+
+#@<> WL16958-TSFR_3_1_2 - read deleted secret
+with TEST("plaintext"):
+    # store then delete
+    EXPECT_NO_THROWS(lambda: shell.store_secret(test_key, test_secret))
+    EXPECT_NO_THROWS(lambda: shell.delete_secret(test_key))
+    # read fails
+    EXPECT_THROWS(lambda: shell.read_secret(test_key), "RuntimeError: Failed to read the secret: Could not find the secret")
+
+#@<> WL16958-TSFR_3_2_1 - unknown key
+with TEST("plaintext"):
+    EXPECT_THROWS(lambda: shell.delete_secret(test_key), "RuntimeError: Failed to delete the secret: Could not find the secret")
+
+#@<> WL16958-TSFR_4_1 - delete all secrets with no secrets stored
+with TEST("plaintext"):
+    EXPECT_NO_THROWS(lambda: shell.delete_all_secrets())
+
+#@<> WL16958-TSFR_4_2 - store some secrets, then delete all of them
+with TEST("plaintext"):
+    # store
+    EXPECT_NO_THROWS(lambda: shell.store_secret(test_key, test_secret))
+    EXPECT_NO_THROWS(lambda: shell.store_secret(2 * test_key, 2 * test_secret))
+    # delete
+    EXPECT_NO_THROWS(lambda: shell.delete_all_secrets())
+    # test
+    EXPECT_NO_THROWS(lambda: list_all_secrets())
+    EXPECT_EQ([], actual_all_secrets)
+    EXPECT_THROWS(lambda: shell.read_secret(test_key), "RuntimeError: Failed to read the secret: Could not find the secret")
+
+#@<> WL16958-TSFR_5_1 - store some secrets, then list them
+with TEST("plaintext"):
+    # store
+    EXPECT_NO_THROWS(lambda: shell.store_secret(test_key, test_secret))
+    EXPECT_NO_THROWS(lambda: shell.store_secret(2 * test_key, 2 * test_secret))
+    # list
+    EXPECT_NO_THROWS(lambda: list_all_secrets())
+    # test
+    EXPECT_EQ(2, len(actual_all_secrets))
+    EXPECT_EQ({test_key, 2 * test_key}, set(actual_all_secrets))
+
+#@<> WL16958-TSFR_5_2 - store some secrets, store a credential, then list secrets
+with TEST("plaintext"):
+    # store
+    EXPECT_NO_THROWS(lambda: shell.store_secret(test_key, test_secret))
+    EXPECT_NO_THROWS(lambda: shell.store_secret(2 * test_key, 2 * test_secret))
+    EXPECT_NO_THROWS(lambda: shell.store_credential("user@host", "pass"))
+    # list
+    EXPECT_NO_THROWS(lambda: list_all_secrets())
+    # test
+    EXPECT_EQ(2, len(actual_all_secrets))
+    EXPECT_EQ({test_key, 2 * test_key}, set(actual_all_secrets))
+
+#@<> WL16958-TSFR_6_1 - store a secret and a credential using the same ID
+with TEST("plaintext"):
+    key = "user@host"
+    # store
+    EXPECT_NO_THROWS(lambda: shell.store_secret(key, test_secret))
+    EXPECT_NO_THROWS(lambda: shell.store_credential(key, "pass"))
+    # list + read
+    EXPECT_NO_THROWS(lambda: list_all_secrets())
+    EXPECT_NO_THROWS(lambda: read_secret(key))
+    # test
+    EXPECT_EQ([key], actual_all_secrets)
+    EXPECT_EQ(test_secret, actual_secret)
+
+#@<> WL16958-ET_1 - multiple helpers
+with TEST("plaintext"):
+    # store
+    EXPECT_NO_THROWS(lambda: shell.store_secret(test_key, test_secret))
+    # switch to a different helper
+    alternative_helper = "windows-credential" if "windows" == __os_type else "login-path"
+    EXPECT_NO_THROWS(lambda: set_helper(alternative_helper))
+    # read fails
+    EXPECT_THROWS(lambda: shell.read_secret(test_key), "RuntimeError: Failed to read the secret: Could not find the secret")
+    # switch back
+    EXPECT_NO_THROWS(lambda: set_helper("plaintext"))
+    # read succeeds
+    EXPECT_NO_THROWS(lambda: read_secret(test_key))
+    # test
+    EXPECT_EQ(test_secret, actual_secret)
+
+#@<> Cleanup
+wipe_all_secrets()
+set_helper(current_helper)
diff --git a/unittest/scripts/auto/py_shell/scripts/shell_ssh_adminapi_norecord.py b/unittest/scripts/auto/py_shell/scripts/shell_ssh_adminapi_norecord.py
index dd8deaac2b..47566e8a74 100644
--- a/unittest/scripts/auto/py_shell/scripts/shell_ssh_adminapi_norecord.py
+++ b/unittest/scripts/auto/py_shell/scripts/shell_ssh_adminapi_norecord.py
@@ -1,4 +1,4 @@
-#@ {has_ssh_environment() and __version_num >= 80000}
+#@ {has_ssh_environment() and __version_num >= __mysh_version_num}
 
 from _ssh_utils import *
 from pathlib import Path
@@ -15,6 +15,7 @@
 """)
 
 shell.options["credentialStore.helper"] = "plaintext"
+EXPECT_NO_THROWS(lambda: shell.delete_all_credentials())
 
 #@<> Initial connection, adds the server to the known hosts file
 shell.options.useWizards = True
@@ -33,6 +34,7 @@
 for f in funcs:
     EXPECT_THROWS(lambda: dba[f]("test"), "InnoDB cluster functionality is not available through SSH tunneling")
 
+session.close()
 
 #@<>  WL#14246-TSFR_5_2 connect to the sshd and test sandbox commands
 testutil.expect_prompt("Save password for 'ssh://{}'? ".format(SSH_URI_NOPASS), "N")
@@ -106,3 +108,9 @@
     testutil.call_mysqlsh(["--credential-store-helper=plaintext", "--ssh", SSH_URI_NOPASS, MYSQL_OVER_SSH_URI, "--ssh-config-file", config_file, "--js", "-e", "dba.{}('test');".format(fun)], "", None, os.path.join(__bin_dir, "mysqlsh"))
     EXPECT_STDOUT_CONTAINS("InnoDB cluster functionality is not available through SSH tunneling")
     WIPE_STDOUT()
+
+#@<> BUG#33564687 all tunnels should be closed at this point
+EXPECT_EQ(0, len(shell.list_ssh_connections()))
+
+#@<> cleanup
+EXPECT_NO_THROWS(lambda: shell.delete_all_credentials())
diff --git a/unittest/scripts/auto/py_shell/scripts/shell_ssh_errors_norecord.py b/unittest/scripts/auto/py_shell/scripts/shell_ssh_errors_norecord.py
index aa4bc83263..7ec76d0d47 100644
--- a/unittest/scripts/auto/py_shell/scripts/shell_ssh_errors_norecord.py
+++ b/unittest/scripts/auto/py_shell/scripts/shell_ssh_errors_norecord.py
@@ -1,4 +1,4 @@
-#@ {has_ssh_environment() and __version_num >= 80000}
+#@ {has_ssh_environment() and __version_num >= __mysh_version_num}
 
 from _ssh_utils import *
 from pathlib import Path
@@ -73,7 +73,7 @@
 ####################
 
 shell.options["credentialStore.helper"] = "plaintext"
-
+EXPECT_NO_THROWS(lambda: shell.delete_all_credentials())
 
 ####################
 create_ssh_conf(config_file, f"""
@@ -173,7 +173,7 @@
 shell.options["useWizards"] = False
 conn = {'uri':None, "ssh": SSH_URI_NOPASS, "ssh-config-file": config_file}
 conn["uri"] = "mysql://user@invalid_host:3306"
-EXPECT_THROWS(lambda: shell.connect(conn), "Error opening MySQL session through SSH tunnel: Lost connection to MySQL server at 'waiting for initial communication packet'")
+EXPECT_THROWS(lambda: shell.connect(conn), "Error opening MySQL session through SSH tunnel: Lost connection to MySQL server at '")
 conn["uri"] = ""
 EXPECT_THROWS(lambda: shell.connect(conn), "Invalid URI: Expected token at position 0 but no tokens left")
 conn["uri"] = "mysql://user@host name:3306"
@@ -186,3 +186,9 @@
 EXPECT_THROWS(lambda: shell.connect(conn), "Invalid URI: Expected token at position 0 but no tokens left")
 conn = {"uri":"mysqlx://user@host name:33060"}
 EXPECT_THROWS(lambda: shell.connect(conn), "Invalid URI: Illegal space found at position 18")
+
+#@<> BUG#33564687 all tunnels should be closed at this point
+EXPECT_EQ(0, len(shell.list_ssh_connections()))
+
+#@<> cleanup
+EXPECT_NO_THROWS(lambda: shell.delete_all_credentials())
diff --git a/unittest/scripts/auto/py_shell/scripts/shell_ssh_import_export_norecord.py b/unittest/scripts/auto/py_shell/scripts/shell_ssh_import_export_norecord.py
index 71132e775e..be68eb6e54 100644
--- a/unittest/scripts/auto/py_shell/scripts/shell_ssh_import_export_norecord.py
+++ b/unittest/scripts/auto/py_shell/scripts/shell_ssh_import_export_norecord.py
@@ -1,4 +1,4 @@
-#@ {has_ssh_environment() and __version_num == __mysh_version_num}
+#@ {has_ssh_environment() and __version_num >= __mysh_version_num}
 
 from _ssh_utils import *
 from pathlib import Path
@@ -214,9 +214,10 @@ def compare_instances():
 if __mysh_version_num >= mysql_over_ssh_version:
   # Creates a dump using X protocol
   EXPECT_NO_THROWS(lambda: util.dump_instance(os.path.join(outdir, "dump_from_ssh_x")), "Unable to dump instance")
-  session.close()
   EXPECT_STDOUT_CONTAINS("Schemas dumped: 1")
 
+session.close()
+
 # Clean the data
 remote_session = mysql.get_session({"uri": MYSQL_OVER_SSH_URI, "ssh": SSH_URI_NOPASS, "ssh-password": SSH_PASS, "ssh-config-file": config_file})
 clean_server(remote_session)
@@ -225,3 +226,6 @@ def compare_instances():
 #@<>check if ssh connections are properly closed, since util.*dump create multiple connections.
 testutil.stop_sandbox(__mysql_sandbox_port1, {"wait":1})
 testutil.destroy_sandbox(__mysql_sandbox_port1)
+
+#@<> BUG#33564687 all tunnels should be closed at this point
+EXPECT_EQ(0, len(shell.list_ssh_connections()))
diff --git a/unittest/scripts/auto/py_shell/scripts/shell_ssh_norecord.py b/unittest/scripts/auto/py_shell/scripts/shell_ssh_norecord.py
index 1657da8afc..9338b4190d 100644
--- a/unittest/scripts/auto/py_shell/scripts/shell_ssh_norecord.py
+++ b/unittest/scripts/auto/py_shell/scripts/shell_ssh_norecord.py
@@ -1,4 +1,4 @@
-#@ {has_ssh_environment() and __version_num >= 80000}
+#@ {has_ssh_environment() and __version_num >= __mysh_version_num}
 
 from _ssh_utils import *
 from pathlib import Path
@@ -24,7 +24,7 @@
   key_with_pw_uri = "file:/{}".format(key_with_pw)
 
 shell.options["credentialStore.helper"] = "plaintext"
-shell.delete_all_credentials()
+EXPECT_NO_THROWS(lambda: shell.delete_all_credentials())
 
 #@<> Check config file permission
 default_config = f"""
@@ -641,3 +641,9 @@ def check_connection(obj, connection_data):
 WIPE_OUTPUT()
 
 EXPECT_THROWS(lambda: shell.options.set("ssh.bufferSize", -1), "ValueError: ssh.bufferSize: value out of range")
+
+#@<> BUG#33564687 all tunnels should be closed at this point
+EXPECT_EQ(0, len(shell.list_ssh_connections()))
+
+#@<> cleanup
+EXPECT_NO_THROWS(lambda: shell.delete_all_credentials())
diff --git a/unittest/scripts/auto/py_shell/scripts/sql_handler_decorator_norecord.py b/unittest/scripts/auto/py_shell/scripts/sql_handler_decorator_norecord.py
index 8758d02df6..864c4bb03c 100644
--- a/unittest/scripts/auto/py_shell/scripts/sql_handler_decorator_norecord.py
+++ b/unittest/scripts/auto/py_shell/scripts/sql_handler_decorator_norecord.py
@@ -331,6 +331,37 @@ def my_handler(session, sql):
 Tables_in_mysql (func)
 """)
 
+
+#@<> SQL Handler - Comment Handling API
+def my_handler(session, sql):
+    "Logger for list statements"
+    print(f"====> SQL HANDLER: {sql}")
+    return session.run_sql(sql.replace('list', 'show'))
+
+shell.register_sql_handler('listHandler', 'testing sql handler', ["LIST DATABASES", "LIST TABLES"], my_handler)
+shell.connect(__mysqluripwd)
+
+
+session.run_sql("/* prefix comment */ list databases")
+EXPECT_STDOUT_CONTAINS("""====> SQL HANDLER: /* prefix comment */ list databases""")
+WIPE_OUTPUT()
+
+session.run_sql("list /* middle comment */ databases")
+EXPECT_STDOUT_CONTAINS("""====> SQL HANDLER: list /* middle comment */ databases""")
+WIPE_OUTPUT()
+
+session.run_sql("/* prefix comment */ list /* middle comment */ databases")
+EXPECT_STDOUT_CONTAINS("""====> SQL HANDLER: /* prefix comment */ list /* middle comment */ databases""")
+WIPE_OUTPUT()
+
+session.run_sql("# Full line comment\nlist databases")
+EXPECT_STDOUT_CONTAINS("""====> SQL HANDLER: # Full line comment\nlist databases""")
+WIPE_OUTPUT()
+
+session.run_sql("-- Full line comment\nlist databases")
+EXPECT_STDOUT_CONTAINS("""====> SQL HANDLER: -- Full line comment\nlist databases""")
+WIPE_OUTPUT()
+
 # @<> Finalization
 testutil.rmfile(script_path)
 testutil.rmdir(plugins_path, True)
diff --git a/unittest/scripts/auto/py_shell/scripts/upgrade_checker_norecord.py b/unittest/scripts/auto/py_shell/scripts/upgrade_checker_norecord.py
index 693668044d..1ff7495777 100644
--- a/unittest/scripts/auto/py_shell/scripts/upgrade_checker_norecord.py
+++ b/unittest/scripts/auto/py_shell/scripts/upgrade_checker_norecord.py
@@ -215,5 +215,66 @@
 session.run_sql("SET GLOBAL sql_mode = @saved_sql_mode")
 session.run_sql("SET SESSION sql_mode = @saved_sql_mode")
 
+
+#@<> Invalid FK References Check - Cleanup
+session.run_sql("DROP SCHEMA uctest3")
+
+#@<> Bug#37651453 false positive on FK to different schema - full key to other schema table - no warning
+session.run_sql("create schema fktestone;")
+session.run_sql("create schema fktesttwo;")
+
+session.run_sql("use fktestone;")
+session.run_sql("""CREATE TABLE `source_table` (
+    `id_key` bigint unsigned NOT NULL AUTO_INCREMENT,
+    `other` varchar(255) DEFAULT NULL,
+    PRIMARY KEY (`id_key`)
+    ) ENGINE=InnoDB;""")
+
+session.run_sql("use fktesttwo;")
+session.run_sql("""CREATE TABLE `target_table` (
+`id` bigint unsigned NOT NULL AUTO_INCREMENT,
+`id_key` bigint unsigned DEFAULT NULL,
+PRIMARY KEY (`id`),
+KEY `idx_id_key` (`id_key`),
+CONSTRAINT `fk_target_table_id_key` FOREIGN KEY (`id_key`) REFERENCES `fktestone`.`source_table` (`id_key`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB;""")
+
+testutil.call_mysqlsh([__sandbox_uri1, "--", "util", "check-for-server-upgrade", "--include=foreignKeyReferences"], "", ["MYSQLSH_TERM_COLOR_MODE=nocolor"])
+EXPECT_OUTPUT_NOT_CONTAINS("fk_target_table_id_key")
+EXPECT_OUTPUT_CONTAINS("Warnings: 0")
+
+#@<> false positive FK - no warning - cleanup
+session.run_sql("drop schema if exists fktesttwo;")
+session.run_sql("drop schema if exists fktestone;")
+
+#@<> Bug#37651453 false positive on FK to different schema - partial key to other schema table - with warning
+session.run_sql("create schema fktestone;")
+session.run_sql("create schema fktesttwo;")
+
+session.run_sql("use fktestone;")
+session.run_sql("""CREATE TABLE `source_table` (
+  `id_key` bigint unsigned NOT NULL,
+  `id_key_other` bigint unsigned NOT NULL,
+  `other` varchar(255) DEFAULT NULL,
+  PRIMARY KEY (`id_key`, `id_key_other`)
+) ENGINE=InnoDB;""")
+
+session.run_sql("use fktesttwo;")
+session.run_sql("""CREATE TABLE `target_table` (
+  `id` bigint unsigned NOT NULL,
+  `id_key` bigint unsigned DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `idx_id_key` (`id_key`),
+  CONSTRAINT `fk_target_table_id_key` FOREIGN KEY (`id_key`) REFERENCES `fktestone`.`source_table` (`id_key`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB;""")
+
+testutil.call_mysqlsh([__sandbox_uri1, "--", "util", "check-for-server-upgrade", "--include=foreignKeyReferences"], "", ["MYSQLSH_TERM_COLOR_MODE=nocolor"])
+EXPECT_OUTPUT_CONTAINS("fk_target_table_id_key")
+EXPECT_OUTPUT_NOT_CONTAINS("Warnings: 0")
+
+#@<> false positive FK - with warning - cleanup
+session.run_sql("drop schema if exists fktesttwo;")
+session.run_sql("drop schema if exists fktestone;")
+
 #@<> Cleanup
-testutil.destroy_sandbox(__mysql_sandbox_port1)
\ No newline at end of file
+testutil.destroy_sandbox(__mysql_sandbox_port1)
diff --git a/unittest/scripts/auto/py_shell/scripts/upgrade_checker_timeout_norecord.py b/unittest/scripts/auto/py_shell/scripts/upgrade_checker_timeout_norecord.py
index 4d8a2a21ea..c551a2d67c 100644
--- a/unittest/scripts/auto/py_shell/scripts/upgrade_checker_timeout_norecord.py
+++ b/unittest/scripts/auto/py_shell/scripts/upgrade_checker_timeout_norecord.py
@@ -4,6 +4,7 @@
 shell.connect(__mysql_uri)
 session.run_sql("set @OLD_SQL_MODE = @@SQL_MODE;")
 session.run_sql("set @@SQL_MODE = 'NO_AUTO_CREATE_USER,NO_FIELD_OPTIONS,NO_KEY_OPTIONS';")
+session.run_sql("drop schema if exists test;")
 session.run_sql("CREATE SCHEMA test;")
 session.run_sql("USE test;")
 session.run_sql("create table Clone(COMPONENT integer, cube int);")
diff --git a/unittest/scripts/auto/py_shell/scripts/util_copy_instance_norecord.py b/unittest/scripts/auto/py_shell/scripts/util_copy_instance_norecord.py
index 96a414492c..0678290850 100644
--- a/unittest/scripts/auto/py_shell/scripts/util_copy_instance_norecord.py
+++ b/unittest/scripts/auto/py_shell/scripts/util_copy_instance_norecord.py
@@ -91,7 +91,7 @@ def EXPECT_FAIL(error, msg, connectionData, options = {}, src = __sandbox_uri1,
 TEST_ARRAY_OF_STRINGS_OPTION("includeUsers")
 
 #@<> WL15298_TSFR_1_2_1_8
-EXPECT_SUCCESS(__sandbox_uri2, { "includeUsers": [ test_user_account ] })
+EXPECT_SUCCESS(__sandbox_uri2, { "includeUsers": [ test_user_account ], "ddlOnly": True})
 EXPECT_STDOUT_MATCHES(re.compile(r"1 out of \d users will be dumped"))
 EXPECT_STDOUT_CONTAINS("1 accounts were loaded")
 
@@ -140,7 +140,7 @@ def EXPECT_FAIL(error, msg, connectionData, options = {}, src = __sandbox_uri1,
 
 #@<> WL15298_TSFR_4_4_8 {VER(>=8.0.24)}
 # this tests that compatibility mode is recognized (there's no error)
-EXPECT_SUCCESS(__sandbox_uri2, { "compatibility": [ "create_invisible_pks" ] })
+EXPECT_SUCCESS(__sandbox_uri2, { "compatibility": [ "create_invisible_pks" ], "ddlOnly": True })
 
 #@<> WL15298_TSFR_4_4_9
 # WL15298_TSFR_4_4_10
@@ -168,10 +168,10 @@ def EXPECT_FAIL(error, msg, connectionData, options = {}, src = __sandbox_uri1,
     EXPECT_STDOUT_NOT_CONTAINS("'targetVersion'")
 
 #@<> WL15298_TSFR_4_4_19
-EXPECT_SUCCESS(__sandbox_uri2, { "triggers": True })
+EXPECT_SUCCESS(__sandbox_uri2, { "triggers": True, "ddlOnly": True })
 
 #@<> WL15298_TSFR_4_4_20
-EXPECT_SUCCESS(__sandbox_uri2, { "triggers": False })
+EXPECT_SUCCESS(__sandbox_uri2, { "triggers": False, "ddlOnly": True })
 
 #@<> WL15298_TSFR_4_4_21
 TEST_BOOL_OPTION("triggers")
@@ -180,19 +180,19 @@ def EXPECT_FAIL(error, msg, connectionData, options = {}, src = __sandbox_uri1,
 TEST_ARRAY_OF_STRINGS_OPTION("excludeTriggers")
 
 #@<> WL15298_TSFR_4_4_22
-EXPECT_SUCCESS(__sandbox_uri2, { "excludeTriggers": [] })
+EXPECT_SUCCESS(__sandbox_uri2, { "excludeTriggers": [], "ddlOnly": True })
 
 #@<> WL15298_TSFR_4_4_23
-EXPECT_SUCCESS(__sandbox_uri2, { "excludeTriggers": [ "sakila.customer", "sakila.wrong", "wrong.wrong", "sakila.film.ins_film", "sakila.film.wrong" ] })
+EXPECT_SUCCESS(__sandbox_uri2, { "excludeTriggers": [ "sakila.customer", "sakila.wrong", "wrong.wrong", "sakila.film.ins_film", "sakila.film.wrong" ], "ddlOnly": True })
 
 #@<> WL15298 - test invalid values of includeTriggers option
 TEST_ARRAY_OF_STRINGS_OPTION("includeTriggers")
 
 #@<> WL15298_TSFR_4_4_27
-EXPECT_SUCCESS(__sandbox_uri2, { "includeTriggers": [] })
+EXPECT_SUCCESS(__sandbox_uri2, { "includeTriggers": [], "ddlOnly": True })
 
 #@<> WL15298_TSFR_4_4_28
-EXPECT_SUCCESS(__sandbox_uri2, { "includeTriggers": [ "sakila.customer", "sakila.wrong", "wrong.wrong", "sakila.film.ins_film", "sakila.film.wrong" ] })
+EXPECT_SUCCESS(__sandbox_uri2, { "includeTriggers": [ "sakila.customer", "sakila.wrong", "wrong.wrong", "sakila.film.ins_film", "sakila.film.wrong" ], "ddlOnly": True })
 
 #@<> WL15298_TSFR_4_4_31
 EXPECT_SUCCESS(__sandbox_uri2, { "tzUtc": True })
@@ -515,58 +515,58 @@ def EXPECT_FAIL(error, msg, connectionData, options = {}, src = __sandbox_uri1,
 TEST_ARRAY_OF_STRINGS_OPTION("includeTables")
 
 #@<> WL15298_TSFR_5_1_11
-EXPECT_SUCCESS(__sandbox_uri2, { "events": True })
+EXPECT_SUCCESS(__sandbox_uri2, { "events": True, "ddlOnly": True })
 
 #@<> WL15298_TSFR_5_1_12
-EXPECT_SUCCESS(__sandbox_uri2, { "events": False })
+EXPECT_SUCCESS(__sandbox_uri2, { "events": False, "ddlOnly": True })
 
 #@<> WL15298_TSFR_5_1_13
 TEST_BOOL_OPTION("events")
 
 #@<> WL15298_TSFR_5_1_14
-EXPECT_SUCCESS(__sandbox_uri2, { "excludeEvents": [] })
+EXPECT_SUCCESS(__sandbox_uri2, { "excludeEvents": [], "ddlOnly": True })
 
 #@<> WL15298_TSFR_5_1_16
 EXPECT_FAIL("ValueError", f"Argument #{options_arg_no}: The event to be excluded must be in the following form: schema.event, with optional backtick quotes, wrong value: 'event'.", __sandbox_uri2, { "excludeEvents": [ "event" ] })
 
 #@<> WL15298_TSFR_5_1_17
-EXPECT_SUCCESS(__sandbox_uri2, { "excludeEvents": [ "sakila.existing_event", "`sakila`.`wrong`", "wrong.wrong" ] })
+EXPECT_SUCCESS(__sandbox_uri2, { "excludeEvents": [ "sakila.existing_event", "`sakila`.`wrong`", "wrong.wrong" ], "ddlOnly": True })
 
 #@<> WL15298 - test invalid values of excludeEvents option
 TEST_ARRAY_OF_STRINGS_OPTION("excludeEvents")
 
 #@<> WL15298_TSFR_5_1_20
-EXPECT_SUCCESS(__sandbox_uri2, { "includeEvents": [] })
+EXPECT_SUCCESS(__sandbox_uri2, { "includeEvents": [], "ddlOnly": True })
 
 #@<> WL15298_TSFR_5_1_21
-EXPECT_SUCCESS(__sandbox_uri2, { "includeEvents": [ "sakila.existing_event", "`sakila`.`wrong`", "wrong.wrong" ] })
+EXPECT_SUCCESS(__sandbox_uri2, { "includeEvents": [ "sakila.existing_event", "`sakila`.`wrong`", "wrong.wrong" ], "ddlOnly": True })
 
 #@<> WL15298 - test invalid values of includeEvents option
 TEST_ARRAY_OF_STRINGS_OPTION("includeEvents")
 
 #@<> WL15298_TSFR_5_1_23
-EXPECT_SUCCESS(__sandbox_uri2, { "routines": True })
+EXPECT_SUCCESS(__sandbox_uri2, { "routines": True, "ddlOnly": True })
 
 #@<> WL15298 - test routines option
-EXPECT_SUCCESS(__sandbox_uri2, { "routines": False })
+EXPECT_SUCCESS(__sandbox_uri2, { "routines": False, "ddlOnly": True })
 
 #@<> WL15298_TSFR_5_1_24
 TEST_BOOL_OPTION("routines")
 
 #@<> WL15298_TSFR_5_1_25
-EXPECT_SUCCESS(__sandbox_uri2, { "excludeRoutines": [] })
+EXPECT_SUCCESS(__sandbox_uri2, { "excludeRoutines": [], "ddlOnly": True })
 
 #@<> WL15298_TSFR_5_1_26
-EXPECT_SUCCESS(__sandbox_uri2, { "excludeRoutines": [ "`sakila`.`rewards_report`", "sakila.get_customer_balance", "`sakila`.`wrong`", "wrong.wrong" ] })
+EXPECT_SUCCESS(__sandbox_uri2, { "excludeRoutines": [ "`sakila`.`rewards_report`", "sakila.get_customer_balance", "`sakila`.`wrong`", "wrong.wrong" ], "ddlOnly": True })
 
 #@<> WL15298 - test invalid values of excludeRoutines option
 TEST_ARRAY_OF_STRINGS_OPTION("excludeRoutines")
 
 #@<> WL15298 - test includeRoutines option
-EXPECT_SUCCESS(__sandbox_uri2, { "includeRoutines": [] })
+EXPECT_SUCCESS(__sandbox_uri2, { "includeRoutines": [], "ddlOnly": True })
 
 #@<> WL15298_TSFR_5_1_29
-EXPECT_SUCCESS(__sandbox_uri2, { "includeRoutines": [ "`sakila`.`rewards_report`", "sakila.get_customer_balance", "`sakila`.`wrong`", "wrong.wrong" ] })
+EXPECT_SUCCESS(__sandbox_uri2, { "includeRoutines": [ "`sakila`.`rewards_report`", "sakila.get_customer_balance", "`sakila`.`wrong`", "wrong.wrong" ], "ddlOnly": True })
 
 #@<> WL15298 - test invalid values of includeRoutines option
 TEST_ARRAY_OF_STRINGS_OPTION("includeRoutines")
@@ -843,7 +843,7 @@ def setup_db(account):
 for account in account_names:
     src_session.run_sql(f"GRANT SET_ANY_DEFINER ON *.* TO {account}")
     WIPE_OUTPUT()
-    EXPECT_SUCCESS(__sandbox_uri2, { "compatibility": [ "strip_restricted_grants" ], "includeUsers": [ account ], "dryRun": True, "includeSchemas": [ schema_name ], "users": True, "showProgress": False })
+    EXPECT_SUCCESS(__sandbox_uri2, { "compatibility": [ "strip_restricted_grants" ], "includeUsers": [ account ], "dryRun": True, "includeSchemas": [ schema_name ], "users": True, "showProgress": False, "ddlOnly": True })
     EXPECT_STDOUT_NOT_CONTAINS("SET_ANY_DEFINER")
     src_session.run_sql(f"REVOKE SET_ANY_DEFINER ON *.* FROM {account}")
 
@@ -851,7 +851,7 @@ def setup_db(account):
 for account in account_names:
     src_session.run_sql(f"GRANT SET_USER_ID ON *.* TO {account}")
     WIPE_OUTPUT()
-    EXPECT_SUCCESS(__sandbox_uri2, { "compatibility": [ "strip_restricted_grants" ], "includeUsers": [ account ], "dryRun": True, "includeSchemas": [ schema_name ], "users": True, "showProgress": False })
+    EXPECT_SUCCESS(__sandbox_uri2, { "compatibility": [ "strip_restricted_grants" ], "includeUsers": [ account ], "dryRun": True, "includeSchemas": [ schema_name ], "users": True, "showProgress": False, "ddlOnly": True })
     EXPECT_STDOUT_CONTAINS(strip_restricted_grants_set_user_id_replaced(account).fixed(True))
     src_session.run_sql(f"REVOKE SET_USER_ID ON *.* FROM {account}")
 
diff --git a/unittest/scripts/auto/py_shell/scripts/util_copy_schemas_norecord.py b/unittest/scripts/auto/py_shell/scripts/util_copy_schemas_norecord.py
index 4264571230..978c1c6afa 100644
--- a/unittest/scripts/auto/py_shell/scripts/util_copy_schemas_norecord.py
+++ b/unittest/scripts/auto/py_shell/scripts/util_copy_schemas_norecord.py
@@ -108,7 +108,7 @@ def EXPECT_FAIL(error, msg, connectionData, options = {}, src = __sandbox_uri1,
 
 #@<> WL15298_TSFR_4_4_8 {VER(>=8.0.24)}
 # this tests that compatibility mode is recognized (there's no error)
-EXPECT_SUCCESS(__sandbox_uri2, { "compatibility": [ "create_invisible_pks" ] })
+EXPECT_SUCCESS(__sandbox_uri2, { "compatibility": [ "create_invisible_pks" ], "ddlOnly": True })
 
 #@<> WL15298_TSFR_4_4_9
 # WL15298_TSFR_4_4_10
@@ -117,16 +117,16 @@ def EXPECT_FAIL(error, msg, connectionData, options = {}, src = __sandbox_uri1,
 # WL15298_TSFR_4_4_14
 # WL15298_TSFR_4_4_15
 # this tests that compatibility mode is recognized and some of them are applied
-EXPECT_SUCCESS(__sandbox_uri2, { "compatibility": [ "force_innodb", "ignore_missing_pks", "ignore_wildcard_grants", "skip_invalid_accounts", "strip_definers", "strip_invalid_grants", "strip_restricted_grants", "strip_tablespaces" ] })
+EXPECT_SUCCESS(__sandbox_uri2, { "compatibility": [ "force_innodb", "ignore_missing_pks", "ignore_wildcard_grants", "skip_invalid_accounts", "strip_definers", "strip_invalid_grants", "strip_restricted_grants", "strip_tablespaces" ], "ddlOnly": True })
 EXPECT_STDOUT_NOT_CONTAINS(f"User {test_user_account} had restricted privileges")
 EXPECT_STDOUT_CONTAINS("Function `sakila`.`get_customer_balance` had definer clause removed")
 EXPECT_STDOUT_CONTAINS("View `sakila`.`sales_by_store` had SQL SECURITY characteristic set to INVOKER")
 
 #@<> WL15298_TSFR_4_4_19
-EXPECT_SUCCESS(__sandbox_uri2, { "triggers": True })
+EXPECT_SUCCESS(__sandbox_uri2, { "triggers": True, "ddlOnly": True })
 
 #@<> WL15298_TSFR_4_4_20
-EXPECT_SUCCESS(__sandbox_uri2, { "triggers": False })
+EXPECT_SUCCESS(__sandbox_uri2, { "triggers": False, "ddlOnly": True })
 
 #@<> WL15298_TSFR_4_4_21
 TEST_BOOL_OPTION("triggers")
@@ -135,25 +135,25 @@ def EXPECT_FAIL(error, msg, connectionData, options = {}, src = __sandbox_uri1,
 TEST_ARRAY_OF_STRINGS_OPTION("excludeTriggers")
 
 #@<> WL15298_TSFR_4_4_22
-EXPECT_SUCCESS(__sandbox_uri2, { "excludeTriggers": [] })
+EXPECT_SUCCESS(__sandbox_uri2, { "excludeTriggers": [], "ddlOnly": True })
 
 #@<> WL15298_TSFR_4_4_23
-EXPECT_SUCCESS(__sandbox_uri2, { "excludeTriggers": [ "sakila.customer", "sakila.wrong", "sakila.film.ins_film", "sakila.film.wrong" ] })
+EXPECT_SUCCESS(__sandbox_uri2, { "excludeTriggers": [ "sakila.customer", "sakila.wrong", "sakila.film.ins_film", "sakila.film.wrong" ], "ddlOnly": True })
 
 #@<> WL15298 - test invalid values of includeTriggers option
 TEST_ARRAY_OF_STRINGS_OPTION("includeTriggers")
 
 #@<> WL15298_TSFR_4_4_27
-EXPECT_SUCCESS(__sandbox_uri2, { "includeTriggers": [] })
+EXPECT_SUCCESS(__sandbox_uri2, { "includeTriggers": [], "ddlOnly": True })
 
 #@<> WL15298_TSFR_4_4_28
-EXPECT_SUCCESS(__sandbox_uri2, { "includeTriggers": [ "sakila.customer", "sakila.wrong", "sakila.film.ins_film", "sakila.film.wrong" ] })
+EXPECT_SUCCESS(__sandbox_uri2, { "includeTriggers": [ "sakila.customer", "sakila.wrong", "sakila.film.ins_film", "sakila.film.wrong" ], "ddlOnly": True })
 
 #@<> WL15298_TSFR_4_4_31
-EXPECT_SUCCESS(__sandbox_uri2, { "tzUtc": True })
+EXPECT_SUCCESS(__sandbox_uri2, { "tzUtc": True, "ddlOnly": True })
 
 #@<> WL15298_TSFR_4_4_32
-EXPECT_SUCCESS(__sandbox_uri2, { "tzUtc": False })
+EXPECT_SUCCESS(__sandbox_uri2, { "tzUtc": False, "ddlOnly": True })
 
 #@<> WL15298_TSFR_4_4_33
 TEST_BOOL_OPTION("tzUtc")
@@ -416,10 +416,10 @@ def EXPECT_FAIL(error, msg, connectionData, options = {}, src = __sandbox_uri1,
 TEST_BOOL_OPTION("skipBinlog")
 
 #@<> WL15298_TSFR_4_5_33
-EXPECT_SUCCESS(__sandbox_uri2, { "skipBinlog": False })
+EXPECT_SUCCESS(__sandbox_uri2, { "skipBinlog": False, "ddlOnly": True })
 
 #@<> WL15298_TSFR_4_5_34
-EXPECT_SUCCESS(__sandbox_uri2, { "skipBinlog": True })
+EXPECT_SUCCESS(__sandbox_uri2, { "skipBinlog": True, "ddlOnly": True })
 
 #@<> WL15298_TSFR_4_5_35
 TEST_STRING_OPTION("updateGtidSet")
@@ -427,13 +427,13 @@ def EXPECT_FAIL(error, msg, connectionData, options = {}, src = __sandbox_uri1,
 EXPECT_FAIL("ValueError", f"Argument #{options_arg_no}: Invalid value 'wrong' for updateGtidSet option, allowed values: 'append', 'off' and 'replace'.", __sandbox_uri2, { "updateGtidSet": "wrong" })
 
 #@<> WL15298_TSFR_4_5_36 {VER(>=8.0.0)}
-EXPECT_SUCCESS(__sandbox_uri2, { "updateGtidSet": "replace" })
+EXPECT_SUCCESS(__sandbox_uri2, { "updateGtidSet": "replace", "ddlOnly": True })
 
 #@<> WL15298_TSFR_4_5_37 {VER(>=8.0.0)}
-EXPECT_SUCCESS(__sandbox_uri2, { "updateGtidSet": "append" })
+EXPECT_SUCCESS(__sandbox_uri2, { "updateGtidSet": "append", "ddlOnly": True })
 
 #@<> WL15298_TSFR_4_5_38
-EXPECT_SUCCESS(__sandbox_uri2, { "updateGtidSet": "off" })
+EXPECT_SUCCESS(__sandbox_uri2, { "updateGtidSet": "off", "ddlOnly": True })
 
 #@<> WL15298_TSFR_4_5_39 {VER(<8.0.0)}
 EXPECT_FAIL("Error: Shell Error (53013)", "Target MySQL server does not support updateGtidSet:'append'.", __sandbox_uri2, { "updateGtidSet": "append" })
@@ -461,58 +461,58 @@ def EXPECT_FAIL(error, msg, connectionData, options = {}, src = __sandbox_uri1,
 TEST_ARRAY_OF_STRINGS_OPTION("includeTables")
 
 #@<> WL15298_TSFR_5_1_11
-EXPECT_SUCCESS(__sandbox_uri2, { "events": True })
+EXPECT_SUCCESS(__sandbox_uri2, { "events": True, "ddlOnly": True })
 
 #@<> WL15298_TSFR_5_1_12
-EXPECT_SUCCESS(__sandbox_uri2, { "events": False })
+EXPECT_SUCCESS(__sandbox_uri2, { "events": False, "ddlOnly": True })
 
 #@<> WL15298_TSFR_5_1_13
 TEST_BOOL_OPTION("events")
 
 #@<> WL15298_TSFR_5_1_14
-EXPECT_SUCCESS(__sandbox_uri2, { "excludeEvents": [] })
+EXPECT_SUCCESS(__sandbox_uri2, { "excludeEvents": [], "ddlOnly": True })
 
 #@<> WL15298_TSFR_5_1_16
 EXPECT_FAIL("ValueError", f"Argument #{options_arg_no}: The event to be excluded must be in the following form: schema.event, with optional backtick quotes, wrong value: 'event'.", __sandbox_uri2, { "excludeEvents": [ "event" ] })
 
 #@<> WL15298_TSFR_5_1_17
-EXPECT_SUCCESS(__sandbox_uri2, { "excludeEvents": [ "sakila.existing_event", "`sakila`.`wrong`" ] })
+EXPECT_SUCCESS(__sandbox_uri2, { "excludeEvents": [ "sakila.existing_event", "`sakila`.`wrong`" ], "ddlOnly": True })
 
 #@<> WL15298 - test invalid values of excludeEvents option
 TEST_ARRAY_OF_STRINGS_OPTION("excludeEvents")
 
 #@<> WL15298_TSFR_5_1_20
-EXPECT_SUCCESS(__sandbox_uri2, { "includeEvents": [] })
+EXPECT_SUCCESS(__sandbox_uri2, { "includeEvents": [], "ddlOnly": True })
 
 #@<> WL15298_TSFR_5_1_21
-EXPECT_SUCCESS(__sandbox_uri2, { "includeEvents": [ "sakila.existing_event", "`sakila`.`wrong`" ] })
+EXPECT_SUCCESS(__sandbox_uri2, { "includeEvents": [ "sakila.existing_event", "`sakila`.`wrong`" ], "ddlOnly": True })
 
 #@<> WL15298 - test invalid values of includeEvents option
 TEST_ARRAY_OF_STRINGS_OPTION("includeEvents")
 
 #@<> WL15298_TSFR_5_1_23
-EXPECT_SUCCESS(__sandbox_uri2, { "routines": True })
+EXPECT_SUCCESS(__sandbox_uri2, { "routines": True, "ddlOnly": True })
 
 #@<> WL15298 - test routines option
-EXPECT_SUCCESS(__sandbox_uri2, { "routines": False })
+EXPECT_SUCCESS(__sandbox_uri2, { "routines": False, "ddlOnly": True })
 
 #@<> WL15298_TSFR_5_1_24
 TEST_BOOL_OPTION("routines")
 
 #@<> WL15298_TSFR_5_1_25
-EXPECT_SUCCESS(__sandbox_uri2, { "excludeRoutines": [] })
+EXPECT_SUCCESS(__sandbox_uri2, { "excludeRoutines": [], "ddlOnly": True })
 
 #@<> WL15298_TSFR_5_1_26
-EXPECT_SUCCESS(__sandbox_uri2, { "excludeRoutines": [ "`sakila`.`rewards_report`", "sakila.get_customer_balance", "`sakila`.`wrong`" ] })
+EXPECT_SUCCESS(__sandbox_uri2, { "excludeRoutines": [ "`sakila`.`rewards_report`", "sakila.get_customer_balance", "`sakila`.`wrong`" ], "ddlOnly": True })
 
 #@<> WL15298 - test invalid values of excludeRoutines option
 TEST_ARRAY_OF_STRINGS_OPTION("excludeRoutines")
 
 #@<> WL15298 - test includeRoutines option
-EXPECT_SUCCESS(__sandbox_uri2, { "includeRoutines": [] })
+EXPECT_SUCCESS(__sandbox_uri2, { "includeRoutines": [], "ddlOnly": True })
 
 #@<> WL15298_TSFR_5_1_29
-EXPECT_SUCCESS(__sandbox_uri2, { "includeRoutines": [ "`sakila`.`rewards_report`", "sakila.get_customer_balance", "`sakila`.`wrong`" ] })
+EXPECT_SUCCESS(__sandbox_uri2, { "includeRoutines": [ "`sakila`.`rewards_report`", "sakila.get_customer_balance", "`sakila`.`wrong`" ], "ddlOnly": True })
 
 #@<> WL15298 - test invalid values of includeRoutines option
 TEST_ARRAY_OF_STRINGS_OPTION("includeRoutines")
@@ -745,7 +745,7 @@ def setup_db(account):
 setup_db(test_user_account)
 
 #@<> WL15887-TSFR_3_2_1 - valid account {not __dbug_off and VER(>=8.2.0)}
-EXPECT_SUCCESS(__sandbox_uri2, { "dryRun": True, "showProgress": False }, schemas = [ schema_name ])
+EXPECT_SUCCESS(__sandbox_uri2, { "dryRun": True, "showProgress": False, "ddlOnly": True }, schemas = [ schema_name ])
 
 # no warnings about DEFINER=
 EXPECT_STDOUT_NOT_CONTAINS(strip_definers_definer_clause(schema_name, test_schema_event, "Event", test_user_account).warning(True))
@@ -763,7 +763,7 @@ def setup_db(account):
 EXPECT_STDOUT_CONTAINS(definer_clause_uses_unknown_account_once().warning(True))
 
 #@<> WL15887-TSFR_4_1 - note about strip_definers {not __dbug_off and VER(>=8.2.0)}
-EXPECT_SUCCESS(__sandbox_uri2, { "compatibility": [ "strip_definers" ], "dryRun": True, "showProgress": False }, schemas = [ schema_name ])
+EXPECT_SUCCESS(__sandbox_uri2, { "compatibility": [ "strip_definers" ], "dryRun": True, "showProgress": False, "ddlOnly": True }, schemas = [ schema_name ])
 EXPECT_STDOUT_CONTAINS(f"NOTE: The 'targetVersion' option is set to {__version}. This version supports the SET_ANY_DEFINER privilege, using the 'strip_definers' compatibility option is unnecessary.")
 
 #@<> WL15887 - cleanup {not __dbug_off and VER(>=8.2.0)}
diff --git a/unittest/scripts/auto/py_shell/scripts/util_debug_diags_norecord.py b/unittest/scripts/auto/py_shell/scripts/util_debug_diags_norecord.py
index dadf9e697c..a87229e095 100644
--- a/unittest/scripts/auto/py_shell/scripts/util_debug_diags_norecord.py
+++ b/unittest/scripts/auto/py_shell/scripts/util_debug_diags_norecord.py
@@ -84,7 +84,7 @@ def check(outpath):
 # inject some binary junk into the log
 shell.log("info", b"test\xed\xf0")
 
-outpath = run_collect(__sandbox_uri1, None)
+outpath = run_collect(__sandbox_uri1, None, hostInfo=0)
 EXPECT_FILE_CONTENTS(outpath, "mysqlsh.log", b"test\xed\xf0")
 
 #@<> ensure query works with tables with more than one blob (or similar) columns
@@ -202,7 +202,7 @@ def check(outpath):
 
 #@<> output is a directory, default filename with timestamp - all
 # TSFR_1_3_4
-run_collect(hostname_uri, outdir+"/")
+run_collect(hostname_uri, outdir+"/", hostInfo=0)
 filenames = [f for f in os.listdir(outdir) if f.startswith("mysql-diagnostics-")]
 EXPECT_EQ(1, len(filenames))
 
@@ -223,7 +223,7 @@ def check(outpath):
 EXPECT_STDOUT_CONTAINS("NOTE: Target server is not at localhost, host information was not collected")
 
 #@<> slowQueries fail - basic
-outpath = run_collect(hostname_uri, None, {"slowQueries":1})
+outpath = run_collect(hostname_uri, None, {"slowQueries":1, "hostInfo":0})
 EXPECT_STDOUT_CONTAINS("slowQueries option requires slow_query_log to be enabled and log_output to be set to TABLE")
 EXPECT_NO_FILE(outpath)
 
@@ -232,23 +232,23 @@ def check(outpath):
 session.run_sql("set session long_query_time=0.1")
 session.run_sql("set global log_output='TABLE'")
 session.run_sql("select sleep(1)")
-outpath = run_collect(hostname_uri, None, {"slowQueries":1})
+outpath = run_collect(hostname_uri, None, {"slowQueries":1, "hostInfo":0})
 CHECK_DIAGPACK(outpath, [(0, session1)], is_cluster=False, innodbMutex=False, slowQueries=True)
 
 #@<> with no access - all - TSFR_1_2_4
-outpath = run_collect(f"nothing:@{hostname}:{__mysql_sandbox_port1}", None)
+outpath = run_collect(f"nothing:@{hostname}:{__mysql_sandbox_port1}", None, {"hostInfo":0})
 EXPECT_STDOUT_CONTAINS("Access denied for user 'nothing'@'%'")
 EXPECT_NO_FILE(outpath)
 
 RESET(outpath)
 
-outpath = run_collect_hl(f"nothing:@{hostname}:{__mysql_sandbox_port1}", None)
+outpath = run_collect_hl(f"nothing:@{hostname}:{__mysql_sandbox_port1}", None, {"hostInfo":0})
 EXPECT_STDOUT_CONTAINS("Access denied for user 'nothing'@'%'")
 EXPECT_NO_FILE(outpath)
 
 RESET(outpath)
 
-outpath = run_collect_sq(f"nothing:@{hostname}:{__mysql_sandbox_port1}", None, "select 1")
+outpath = run_collect_sq(f"nothing:@{hostname}:{__mysql_sandbox_port1}", None, "select 1", {"hostInfo":0})
 EXPECT_STDOUT_CONTAINS("Access denied for user 'nothing'@'%'")
 EXPECT_NO_FILE(outpath)
 
@@ -259,17 +259,17 @@ def check(outpath):
 EXPECT_NO_FILE(outpath)
 
 #@<> with select only access
-outpath = run_collect(f"selectonly:@{hostname}:{__mysql_sandbox_port1}", None)
+outpath = run_collect(f"selectonly:@{hostname}:{__mysql_sandbox_port1}", None, {"hostInfo":0})
 EXPECT_STDOUT_CONTAINS("execute command denied to user 'selectonly'@'%' for routine")
 EXPECT_NO_FILE(outpath)
 
 #@<> minimal privs
-outpath = run_collect(f"minimal:@{hostname}:{__mysql_sandbox_port1}", None)
+outpath = run_collect(f"minimal:@{hostname}:{__mysql_sandbox_port1}", None, {"hostInfo":0})
 CHECK_DIAGPACK(outpath, [(0, session1)], is_cluster=False, innodbMutex=False)
 
 #@<> Regular instance + innodbMutex + schemaStatus
-outpath = run_collect(hostname_uri, None, innodbMutex=1, schemaStats=1, allMembers=1)
-CHECK_DIAGPACK(outpath, [(0, session1)], is_cluster=False, innodbMutex=True, schemaStats=True)
+outpath = run_collect(hostname_uri, None, innodbMutex=1, schemaStats=1, allMembers=1, hostInfo=0)
+CHECK_DIAGPACK(outpath, [(0, session1)], is_cluster=False, innodbMutex=True, schemaStats=True, hostInfo=0)
 EXPECT_STDOUT_CONTAINS("NOTE: allMembers enabled, but InnoDB Cluster metadata not found")
 
 #@<> customSql - bad value - TSFR_1_5_1
@@ -336,7 +336,8 @@ def check(outpath):
 # TSFR_1_5_2 TSFR_1_6_3
 CHECK_ALL_ERROR(check, {"customShell": ['echo "custom script"',
                                         'echo "custom script2"',
-                                        'false']}, uri=__sandbox_uri1)
+                                        'false'],
+                        "hostInfo": 0}, uri=__sandbox_uri1)
 
 
 def check(outpath):
@@ -352,7 +353,8 @@ def check(outpath):
 #@<> customSql and customShell - before/during/after - TSFR_1_6_4, TSFR_1_6_7, TSFR_13_1, TSFR_13_2
 options = {
     "customSql": ["before:select 'before'", "during:select 'during'", "during:select 'during2'", "after:select 'after'"],
-    "customShell": ["before:echo before", "during:echo during", "during:echo during2", "after:echo after", "echo $bla" if __os_type != "windows" else "echo %bla%"]
+    "customShell": ["before:echo before", "during:echo during", "during:echo during2", "after:echo after", "echo $bla" if __os_type != "windows" else "echo %bla%"],
+    "hostInfo": 0
 }
 outpath = run_collect_hl(__sandbox_uri1, None, options, env=["bla=inherited"])
 EXPECT_FILE_CONTENTS(outpath, "custom_shell-before.txt", b"^before$")
@@ -379,66 +381,63 @@ def check(outpath):
 RESET(outpath)
 
 #@<> customSql - TSFR_1_5_3
+session1.run_sql("CREATE TABLE IF NOT EXISTS test.preamble(execution char(64), ts timestamp(6))")
+session1.run_sql("delete from test.preamble")
+session1.run_sql("DROP PROCEDURE IF EXISTS test.waitrows")
+session1.run_sql("""CREATE PROCEDURE test.waitrows(num_rows INT)
+BEGIN
+    DECLARE _count INT DEFAULT 0;
+    WHILE _count < num_rows DO
+            SELECT count(*) INTO _count FROM test.preamble;
+    END WHILE;
+END
+""")
 options = {
     "customSql": [
-        "CREATE TABLE IF NOT EXISTS test.preamble(execution TIMESTAMP(6))",
-        "DURING:INSERT INTO test.preamble(execution) VALUES (NOW(6))"
+        "INSERT INTO test.preamble VALUES ('start', now(6))",
+        "DURING:INSERT INTO test.preamble VALUES ('during', now(6))"
     ],
-    "delay": 2
+    "delay": 1,
+    "hostInfo": 0
 }
 def check(outpath):
+    shell.dump_rows(session1.run_sql("select * from test.preamble"))
+    count, = session1.run_sql("select count(*) from test.preamble").fetch_one()
     if __version_num >= 80000:
-        count, dif = session1.run_sql("select count(*), cast(max(execution)-min(execution) as float) from test.preamble").fetch_one()
+        c, dif = session1.run_sql("select count(*), cast(max(ts)-min(ts) as float) from test.preamble where execution <> 'start'").fetch_one()
     else:
-        count, dif = session1.run_sql("select count(*), cast(max(execution)-min(execution) as signed) from test.preamble").fetch_one()
-    EXPECT_BETWEEN(2, 3, count, outpath+" execution count")
-    EXPECT_GT(dif, 1, outpath)
-    # this test is not deterministic
-    # EXPECT_LT(dif, 4, outpath) # ensure iterations are spaced by more than the specified delay, but not too much more
-    if dif > 4:
-        print(f"WARNING: time between iterations > 4??? ({dif})")
-    session1.run_sql("drop table test.preamble")
+        c, dif = session1.run_sql("select count(*), cast(max(ts)-min(ts) as signed) from test.preamble where execution <> 'start'").fetch_one()
+    EXPECT_GE(count, 2, outpath+" total execution count")
+    EXPECT_GE(c, 2, outpath+" during execution count")
+    EXPECT_GE(dif/(c-1), 1, outpath+" time between collections")
+    # non-deterministic, can vary too much randomly under high loads
+    # EXPECT_LT(dif, 2, outpath) # ensure iterations are spaced by more than the specified delay, but not too much more
 
-CHECK_ALL(check, options, nobasic=True, query="select sleep(5)")
+CHECK_ALL(check, options, nobasic=True, query="CALL test.waitrows(3)")
 
 #@<> customSql - TSFR_1_5_4
+session1.run_sql("delete from test.preamble")
+
 options = {
     "customSql": [
-        "before:CREATE TABLE IF NOT EXISTS test.preamble(execution char(64))",
-        "during:INSERT INTO test.preamble(execution) VALUES ('during')",
-        "after:INSERT INTO test.preamble(execution) VALUES ('after') "
+        "before:INSERT INTO test.preamble VALUES ('before', now(6))",
+        "during:INSERT INTO test.preamble VALUES ('during', now(6))",
+        "after:INSERT INTO test.preamble VALUES ('after', now(6)) "
     ],
-    "delay": 2
+    "delay": 1,
+    "hostInfo": 0
 }
+
 def check(outpath):
     rows = session1.run_sql("select * from test.preamble").fetch_all()
-    EXPECT_BETWEEN(2, 4, len(rows))
-    EXPECT_EQ("during", rows[0][0])
+    EXPECT_GE(len(rows), 3)
+    EXPECT_EQ("before", rows[0][0])
     EXPECT_EQ("during", rows[1][0])
-    EXPECT_EQ("after", rows[len(rows)-1][0])
-    session1.run_sql("drop table test.preamble")
+    EXPECT_EQ("after", rows[-1][0])
 
-CHECK_ALL(check, options, nobasic=True, query="select sleep(5)")
-
-#@<> customSql - TSFR_1_5_5
-options = {
-    "customSql": [
-        "SHOW VARIABLES",
-        "before:SHOW VARIABLES",
-        "during:SHOW VARIABLES",
-        "after:SHOW VARIABLES"
-    ],
-    "delay": 1
-}
-def check(outpath):
-    EXPECT_FILE_CONTENTS(outpath, "custom_sql-before-script_0.tsv", b"SHOW VARIABLES")
-    EXPECT_FILE_CONTENTS(outpath, "custom_sql-before-script_1.tsv", b"SHOW VARIABLES")
-    EXPECT_FILE_CONTENTS(outpath, "custom_sql-iteration0-script_0.tsv", b"SHOW VARIABLES")
-    EXPECT_FILE_CONTENTS(outpath, "custom_sql-iteration1-script_0.tsv", b"SHOW VARIABLES")
-    EXPECT_FILE_CONTENTS(outpath, "custom_sql-after-script_0.tsv", b"SHOW VARIABLES")
+CHECK_ALL(check, options, nobasic=True, query="CALL test.waitrows(3)")
 
-# TSFR_1_4_7
-CHECK_ALL(check, options, nobasic=True, query="select sleep(4)")
+# customSql - TSFR_1_5_5 - deleted redundant test
 
 #@<> plain highLoad
 
@@ -450,7 +449,7 @@ def check(outpath):
     EXPECT_FILE_IN_ZIP(outpath, "diagnostics-raw/iteration-3.metrics.tsv")
     EXPECT_FILE_NOT_IN_ZIP(outpath, "diagnostics-raw/iteration-4.metrics.tsv")
 
-outpath = run_collect_hl(hostname_uri, None, {"iterations":3, "delay":1})
+outpath = run_collect_hl(hostname_uri, None, {"iterations":3, "delay":1, "hostInfo":0})
 check(outpath)
 
 #@<> plain all with innodbMutex
@@ -462,7 +461,7 @@ def check(outpath):
     EXPECT_FILE_IN_ZIP(outpath, "diagnostics-raw/iteration-2.metrics.tsv")
     EXPECT_FILE_NOT_IN_ZIP(outpath, "diagnostics-raw/iteration-3.metrics.tsv")
 
-outpath = run_collect_hl(hostname_uri, None, {"delay":1, "innodbMutex":1})
+outpath = run_collect_hl(hostname_uri, None, {"delay":1, "innodbMutex":1, "hostInfo":0})
 check(outpath)
 
 #@<> highLoad + slowQuery with pfsInstrumentation - current
@@ -480,7 +479,7 @@ def check(outpath):
                                         "wait/io": lambda x: 80 < x < 90 })
     CHECK_PFS_CONSUMERS(outpath, "current", default_consumers)
 
-CHECK_ALL(check, {"delay":1, "pfsInstrumentation":"current"}, nobasic=True)
+CHECK_ALL(check, {"delay":1, "pfsInstrumentation":"current", "hostInfo":0}, nobasic=True)
 
 #@<> highLoad + slowQuery with pfsInstrumentation - medium
 if __version_num > 80000:
@@ -491,7 +490,7 @@ def check(outpath):
     CHECK_PFS_INSTRUMENTS(outpath, "medium", {"wait/synch":lambda x: x == 0})
     CHECK_PFS_CONSUMERS(outpath, "medium", consumers)
 
-CHECK_ALL(check, {"delay":1, "pfsInstrumentation":"medium"}, nobasic=True)
+CHECK_ALL(check, {"delay":1, "pfsInstrumentation":"medium", "hostInfo":0}, nobasic=True)
 
 #@<> highLoad + slowQuery with pfsInstrumentation - full
 # TSFR_6_1_1
@@ -500,12 +499,12 @@ def check(outpath):
     CHECK_PFS_INSTRUMENTS(outpath, "full", {})
     CHECK_PFS_CONSUMERS(outpath, "full", ["events_stages_current", "events_stages_history", "events_stages_history_long", "events_statements_cpu", "events_statements_current","events_statements_history","events_statements_history_long","events_transactions_current","events_transactions_history","events_transactions_history_long","events_waits_current","events_waits_history","events_waits_history_long","global_instrumentation","thread_instrumentation","statements_digest"])
 
-CHECK_ALL(check, {"delay":1, "pfsInstrumentation":"full"}, nobasic=True)
+CHECK_ALL(check, {"delay":1, "pfsInstrumentation":"full", "hostInfo":0}, nobasic=True)
 
 
 #@<> highLoad with pfsInstrumentation current and all disabled instruments/consumers
 # TSFR_6_8 (is invalid) TSFR_6_9 TSFR_6_10
-CHECK_ALL(check, {"delay":1, "pfsInstrumentation":"current"}, nobasic=True)
+CHECK_ALL(check, {"delay":1, "pfsInstrumentation":"current", "hostInfo":0}, nobasic=True)
 
 session1.run_sql("call sys.ps_setup_reset_to_default(true)")
 
@@ -514,7 +513,7 @@ def check(outpath):
 def check(outpath):
     EXPECT_STDOUT_CONTAINS("WARNING: performance_schema.setup_consumers is completely disabled")
 
-CHECK_ALL(check, {"delay":1, "pfsInstrumentation":"current"}, nobasic=True)
+CHECK_ALL(check, {"delay":1, "pfsInstrumentation":"current", "hostInfo":0}, nobasic=True)
 
 session1.run_sql("call sys.ps_setup_reset_to_default(true)")
 
@@ -524,7 +523,7 @@ def check(outpath):
 def check(outpath):
     EXPECT_STDOUT_CONTAINS("WARNING: performance_schema.setup_threads is completely disabled")
 
-CHECK_ALL(check, {"delay":1, "pfsInstrumentation":"current"}, nobasic=True)
+CHECK_ALL(check, {"delay":1, "pfsInstrumentation":"current", "hostInfo":0}, nobasic=True)
 
 session1.run_sql("call sys.ps_setup_reset_to_default(true)")
 
@@ -539,7 +538,7 @@ def check(outpath):
 #@<> plain slowQuery
 
 outpath = run_collect_sq(__sandbox_uri1, None, "select * from mysql.user join mysql.db", {"delay":1})
-CHECK_DIAGPACK(outpath, [(None, session1)], innodbMutex=False, allMembers=0, schemaStats=True, slowQueries=True, localTarget=True)
+CHECK_DIAGPACK(outpath, [(None, session1)], innodbMutex=False, allMembers=0, schemaStats=True, slowQueries=True, localTarget=True, hostInfo=0)
 
 #@<> invalid slowQuery - TSFR_9_0_15
 outpath = run_collect_sq(__sandbox_uri1, None, "drop schema information_schema")
diff --git a/unittest/scripts/auto/py_shell/scripts/util_debug_diags_topology_norecord.py b/unittest/scripts/auto/py_shell/scripts/util_debug_diags_topology_norecord.py
index 64c875eb38..ce17c13712 100644
--- a/unittest/scripts/auto/py_shell/scripts/util_debug_diags_topology_norecord.py
+++ b/unittest/scripts/auto/py_shell/scripts/util_debug_diags_topology_norecord.py
@@ -54,29 +54,29 @@
 testutil.wait_member_transactions(__mysql_sandbox_port3, __mysql_sandbox_port1)
 
 #@<> highload and slowQuery
-outpath = run_collect_hl(__sandbox_uri1, None)
-CHECK_DIAGPACK(outpath, [(None, session1)], localTarget=True)
+outpath = run_collect_hl(__sandbox_uri1, None, hostInfo=0)
+CHECK_DIAGPACK(outpath, [(None, session1)], localTarget=True, hostInfo=0)
 
-outpath = run_collect_sq(__sandbox_uri1, None)
-CHECK_DIAGPACK(outpath, [(None, session1)], localTarget=True)
+outpath = run_collect_sq(__sandbox_uri1, None, hostInfo=0)
+CHECK_DIAGPACK(outpath, [(None, session1)], localTarget=True, hostInfo=0)
 
 #@<> cluster with innodbMutex + schemaStats
 testutil.expect_password("Password for root: ", "root")
-outpath = run_collect(hostname_uri, None, innodbMutex=1, schemaStats=1, allMembers=1)
-CHECK_DIAGPACK(outpath, [(1, session1), (2, session2), (3, session3)], is_cluster=True, innodbMutex=True, schemaStats=True)
+outpath = run_collect(hostname_uri, None, innodbMutex=1, schemaStats=1, allMembers=1, hostInfo=0)
+CHECK_DIAGPACK(outpath, [(1, session1), (2, session2), (3, session3)], is_cluster=True, innodbMutex=True, schemaStats=True, hostInfo=0)
 
 #@<> cluster with allMembers=False
-outpath = run_collect(hostname_uri, None, schemaStats=1, allMembers=0)
+outpath = run_collect(hostname_uri, None, schemaStats=1, allMembers=0, hostInfo=0)
 EXPECT_STDOUT_NOT_CONTAINS("Password for root:")
-CHECK_DIAGPACK(outpath, [(1, session1), (2, session2), (3, session3)], is_cluster=True, schemaStats=True, allMembers=False)
+CHECK_DIAGPACK(outpath, [(1, session1), (2, session2), (3, session3)], is_cluster=True, schemaStats=True, allMembers=False, hostInfo=0)
 
 #@<> with bad password
-outpath = run_collect(hostname_uri, None, password="bla", schemaStats=1, allMembers=1)
+outpath = run_collect(hostname_uri, None, password="bla", schemaStats=1, allMembers=1, hostInfo=0)
 EXPECT_STDOUT_CONTAINS("Access denied for user 'root'@'localhost'")
 EXPECT_NO_FILE(outpath)
 
 #@<> minimal privs with innodb cluster
-run_collect("minimal:@localhost:"+str(__mysql_sandbox_port1), None, allMembers=1, password="")
+run_collect("minimal:@localhost:"+str(__mysql_sandbox_port1), None, allMembers=1, password="", hostInfo=0)
 EXPECT_STDOUT_CONTAINS_ONE_OF(["Access denied", "Plugin 'mysql_native_password' is not loaded"])
 
 #@<> Shutdown instance
@@ -88,22 +88,22 @@
 session2.close()
 testutil.stop_sandbox(__mysql_sandbox_port2, {"wait":1})
 
-outpath = run_collect(hostname_uri, None, allMembers=1)
-CHECK_DIAGPACK(outpath, [(1, session1), (2, "MySQL Error (2003): Can't connect to MySQL server on"), (3, session3)], is_cluster=True, innodbMutex=False)
+outpath = run_collect(hostname_uri, None, allMembers=1, hostInfo=0)
+CHECK_DIAGPACK(outpath, [(1, session1), (2, "MySQL Error (2003): Can't connect to MySQL server on"), (3, session3)], is_cluster=True, innodbMutex=False, hostInfo=0)
 
 #@<> Uninstall GR in one of the instances
 
 # NOTE: The util should be able to handle unknown vars, such as the Group Replication ones that become unknown after the plugin is uninstalled
 session1.run_sql("uninstall plugin group_replication")
 
-outpath = run_collect(hostname_uri, None, allMembers=1)
-CHECK_DIAGPACK(outpath, [(1, session1), (2, "MySQL Error (2003): Can't connect to MySQL server on"), (3, session3)], is_cluster=True, innodbMutex=False)
+outpath = run_collect(hostname_uri, None, allMembers=1, hostInfo=0)
+CHECK_DIAGPACK(outpath, [(1, session1), (2, "MySQL Error (2003): Can't connect to MySQL server on"), (3, session3)], is_cluster=True, innodbMutex=False, hostInfo=0)
 
 #@<> Take offline
 session3.run_sql("stop group_replication")
 
-outpath = run_collect(hostname_uri, None, allMembers=1)
-CHECK_DIAGPACK(outpath, [(1, session1), (2, "MySQL Error (2003): Can't connect to MySQL server on"), (3, session3)], is_cluster=True, innodbMutex=False)
+outpath = run_collect(hostname_uri, None, allMembers=1, hostInfo=0)
+CHECK_DIAGPACK(outpath, [(1, session1), (2, "MySQL Error (2003): Can't connect to MySQL server on"), (3, session3)], is_cluster=True, innodbMutex=False, hostInfo=0)
 
 #@<> Expand to ClusterSet {VER(>8.0.0)}
 testutil.start_sandbox(__mysql_sandbox_port2)
diff --git a/unittest/scripts/auto/py_shell/scripts/util_dump_and_load_extra_norecord.py b/unittest/scripts/auto/py_shell/scripts/util_dump_and_load_extra_norecord.py
index f6019a099e..ba70463912 100644
--- a/unittest/scripts/auto/py_shell/scripts/util_dump_and_load_extra_norecord.py
+++ b/unittest/scripts/auto/py_shell/scripts/util_dump_and_load_extra_norecord.py
@@ -42,6 +42,7 @@ def prepare(sbport, options={}):
         "keyring_file_data": datadir+"/keyring",
         "local_infile": "1",
         "tmpdir": mysql_tmpdir,
+        "innodb_doublewrite": "OFF",
         # small sort buffer to force stray filesorts to be triggered even if we don't have much data
         "sort_buffer_size": 32768
     })
@@ -57,10 +58,12 @@ def prepare(sbport, options={}):
 session1.run_sql("create schema tests")
 # BUG#33079172 "SQL SECURITY INVOKER" INSERTED AT WRONG LOCATION
 session1.run_sql("create procedure tests.tmp() IF @v > 0 THEN SELECT 1; ELSE SELECT 2; END IF;;")
+session1.run_sql("/*!80021 alter instance disable innodb redo_log */")
 
 prepare(__mysql_sandbox_port2)
 session2 = mysql.get_session(__sandbox_uri2)
 session2.run_sql("set names utf8mb4")
+session2.run_sql("/*!80021 alter instance disable innodb redo_log */")
 
 #@<> load while dump is still running (prepare)
 # We test this artificially by manually assembling the loaded dump
diff --git a/unittest/scripts/auto/py_shell/scripts/util_dump_and_load_norecord.py b/unittest/scripts/auto/py_shell/scripts/util_dump_and_load_norecord.py
index ed8fcff748..7405eb5f0c 100644
--- a/unittest/scripts/auto/py_shell/scripts/util_dump_and_load_norecord.py
+++ b/unittest/scripts/auto/py_shell/scripts/util_dump_and_load_norecord.py
@@ -37,6 +37,7 @@ def prepare(sbport, options={}):
         "keyring_file_data": os.path.join(datadir, "keyring"),
         "local_infile": "1",
         "tmpdir": mysql_tmpdir,
+        "innodb_doublewrite": "OFF",
         # small sort buffer to force stray filesorts to be triggered even if we don't have much data
         "sort_buffer_size": 32768,
     })
@@ -72,13 +73,16 @@ def TEST_ARRAY_OF_STRINGS_OPTION(option):
 session1.run_sql("create schema tests")
 # BUG#33079172 "SQL SECURITY INVOKER" INSERTED AT WRONG LOCATION
 session1.run_sql("create procedure tests.tmp() IF @v > 0 THEN SELECT 1; ELSE SELECT 2; END IF;;")
+session1.run_sql("/*!80021 alter instance disable innodb redo_log */")
 
 shell.connect(__sandbox_uri1)
 util.dump_instance(os.path.join(outdir, "dump_instance"), {"showProgress": False})
 
-prepare(__mysql_sandbox_port2)
+# BUG#34566034 - load failed when performance_schema was disabled
+prepare(__mysql_sandbox_port2, options={"performance_schema": "OFF"})
 session2 = mysql.get_session(__sandbox_uri2)
 session2.run_sql("set names utf8mb4")
+session2.run_sql("/*!80021 alter instance disable innodb redo_log */")
 
 # BUG#36159820 - accounts which are excluded when dumping with ocimds=true and when loading into MHS
 # BUG#37457569 - added mysql_option_tracker_persister
@@ -91,6 +95,7 @@ def TEST_ARRAY_OF_STRINGS_OPTION(option):
 # * mysql_rest_service_user,
 # * mysql_task_admin,
 # * mysql_task_user,
+# BUG#37792183 - added ocirest - MRS admin in MHS
 mhs_excluded_users = [
     "administrator",
     "mysql_option_tracker_persister",
@@ -105,6 +110,7 @@ def TEST_ARRAY_OF_STRINGS_OPTION(option):
     "ociadmin",
     "ocidbm",
     "ocimonitor",
+    "ocirest",
     "ocirpl",
     "oracle-cloud-agent",
     "rrhhuser",
@@ -472,7 +478,7 @@ def format_rows(rows):
 #@<> BUG#34952027 - same as above, ignore the error
 EXPECT_NO_THROWS(lambda: util.load_dump(dump_dir, { "handleGrantErrors": "ignore", "excludeTables": [ "schema1.table1" ], "loadUsers": True, "excludeUsers": [ "root@%" ], "showProgress": False }), "Load")
 EXPECT_STDOUT_CONTAINS(f"""
-ERROR: While executing user accounts SQL: MySQL Error 1146 (42S02): Table 'schema1.table1' doesn't exist: {grant_on_table};
+ERROR: While applying grants to user accounts: MySQL Error 1146 (42S02): Table 'schema1.table1' doesn't exist: {grant_on_table};
 NOTE: The above error was ignored, the load operation will continue.
 """)
 EXPECT_STDOUT_CONTAINS("1 accounts were loaded, 1 GRANT statement errors were ignored")
@@ -488,7 +494,7 @@ def format_rows(rows):
 #@<> BUG#34952027 - same as above, drop the account
 EXPECT_NO_THROWS(lambda: util.load_dump(dump_dir, { "handleGrantErrors": "drop_account", "excludeTables": [ "schema1.table1" ], "loadUsers": True, "excludeUsers": [ "root@%" ], "showProgress": False }), "Load")
 EXPECT_STDOUT_CONTAINS(f"""
-ERROR: While executing user accounts SQL: MySQL Error 1146 (42S02): Table 'schema1.table1' doesn't exist: {grant_on_table};
+ERROR: While applying grants to user accounts: MySQL Error 1146 (42S02): Table 'schema1.table1' doesn't exist: {grant_on_table};
 NOTE: Due to the above error the account 'user_34952027'@'localhost' was dropped, the load operation will continue.
 """)
 EXPECT_STDOUT_CONTAINS("0 accounts were loaded, 1 accounts were dropped due to GRANT statement errors")
@@ -2689,8 +2695,8 @@ def TEST_STRING_OPTION(option):
 help_text = """
       - ociAuth: string (default: not set) - Use the specified authentication
         method when connecting to the OCI. Allowed values: api_key (used when
-        not explicitly set), instance_principal, resource_principal,
-        security_token.
+        not explicitly set), instance_obo_user, instance_principal,
+        resource_principal, security_token.
 """
 EXPECT_TRUE(help_text in util.help("load_dump"))
 
@@ -2714,7 +2720,7 @@ def TEST_STRING_OPTION(option):
 EXPECT_THROWS(lambda: util.load_dump(dump_dir, { "osBucketName": "bucket", "ociAuth": "resource_principal", "ociProfile": "profile" }), "ValueError: Argument #2: The option 'ociProfile' cannot be used when the 'ociAuth' option is set to: resource_principal.")
 
 #@<> WL15884-TSFR_9_1 - `ociAuth` set to an invalid value
-EXPECT_THROWS(lambda: util.load_dump(dump_dir, { "osBucketName": "bucket", "ociAuth": "unknown" }), "ValueError: Argument #2: Invalid value of 'ociAuth' option, expected one of: api_key, instance_principal, resource_principal, security_token, but got: unknown.")
+EXPECT_THROWS(lambda: util.load_dump(dump_dir, { "osBucketName": "bucket", "ociAuth": "unknown" }), "ValueError: Argument #2: Invalid value of 'ociAuth' option, expected one of: api_key, instance_obo_user, instance_principal, resource_principal, security_token, but got: unknown.")
 
 #@<> WL14387-TSFR_1_1_1 - s3BucketName - string option
 TEST_STRING_OPTION("s3BucketName")
@@ -3099,25 +3105,42 @@ def TEST_STRING_OPTION(option):
 shell.connect(__sandbox_uri1)
 session.run_sql("DROP SCHEMA IF EXISTS !", [tested_schema])
 
-#@<> BUG#34566034 - load failed if "deferTableIndexes": "all", "ignoreExistingObjects": True options were set and instance contained existing tables with indexes {VER(>=8.0.0)}
+#@<> BUG#34566034 - load failed if "deferTableIndexes": "all", "ignoreExistingObjects": True options were set and instance contained existing tables with indexes
 # constants
 dump_dir = os.path.join(outdir, "bug_34566034")
+tested_schema = "tested_schema"
+tested_table = "tested_table"
 
 # setup
 shell.connect(__sandbox_uri2)
-wipeout_server(session)
-c = dba.create_cluster("C")
+session.run_sql("DROP SCHEMA IF EXISTS !", [ tested_schema ])
+session.run_sql("CREATE SCHEMA !", [ tested_schema ])
+session.run_sql("""
+CREATE TABLE IF NOT EXISTS !.! (
+  `cluster_id` CHAR(36) CHARACTER SET ascii COLLATE ascii_general_ci,
+  `cluster_name` VARCHAR(63) NOT NULL,
+  `description` TEXT,
+  `options` JSON,
+  `attributes` JSON,
+  `cluster_type` ENUM('gr', 'ar') NOT NULL,
+  `primary_mode` ENUM('pm', 'mm') NOT NULL DEFAULT 'pm',
+  `router_options` JSON,
+  `clusterset_id` VARCHAR(36) CHARACTER SET ascii COLLATE ascii_general_ci DEFAULT NULL,
+  PRIMARY KEY(cluster_id),
+  UNIQUE KEY(cluster_name)
+) CHARSET = utf8mb4, ROW_FORMAT = DYNAMIC;
+""", [ tested_schema, tested_table ])
 
 # dump
 EXPECT_NO_THROWS(lambda: util.dump_instance(dump_dir, { "showProgress": False }), "Dump should not fail")
 
 # load
 EXPECT_NO_THROWS(lambda: util.load_dump(dump_dir, { "deferTableIndexes": "all", "ignoreExistingObjects": True, "showProgress": False }), "Load should not fail")
+EXPECT_STDOUT_CONTAINS(f"NOTE: Schema `{tested_schema}` already contains a table named `{tested_table}`")
+EXPECT_STDOUT_CONTAINS("NOTE: One or more objects in the dump already exist in the destination database but will be ignored because the 'ignoreExistingObjects' option was enabled.")
 
-#@<> BUG#34566034 - cleanup {VER(>=8.0.0)}
+#@<> BUG#34566034 - cleanup
 shell.connect(__sandbox_uri2)
-c.dissolve({ 'force': True })
-reset_instance(session)
 wipeout_server(session)
 
 #@<> BUG#35304391 - loader should notify if rows were replaced during load
@@ -3700,12 +3723,12 @@ def create_mhs_schemas(s):
 dump_dir = os.path.join(outdir, "bug_36561962")
 progress_file = os.path.join(dump_dir, "progress.json")
 test_schema = "test_schema"
-test_user = "test_user"
+tested_user = "test_user"
 
 # setup
 def create_user(s):
-    s.run_sql(f"DROP USER IF EXISTS {test_user}")
-    s.run_sql(f"CREATE USER {test_user}")
+    s.run_sql(f"DROP USER IF EXISTS {tested_user}")
+    s.run_sql(f"CREATE USER {tested_user}")
 
 def create_objects(s, schema):
     s.run_sql("DROP SCHEMA IF EXISTS !", [ schema ])
@@ -3720,7 +3743,7 @@ def create_objects(s, schema):
         s.run_sql("CREATE LIBRARY !.l LANGUAGE JAVASCRIPT AS $$ $$", [ schema ])
 
 def alter_user(s):
-    s.run_sql(f"ALTER USER {test_user} IDENTIFIED BY 'password1@'")
+    s.run_sql(f"ALTER USER {tested_user} IDENTIFIED BY 'password1@'")
 
 def alter_table(s, schema):
     s.run_sql("ALTER TABLE !.t COMMENT 'modified'", [ schema ])
@@ -3746,7 +3769,7 @@ def alter_objects(s, schema):
 create_objects(session1, test_schema)
 
 shell.connect(__sandbox_uri1)
-EXPECT_NO_THROWS(lambda: util.dump_instance(dump_dir, { "includeSchemas": [test_schema], "includeUsers": [test_user], "showProgress": False }), "Dump should not throw")
+EXPECT_NO_THROWS(lambda: util.dump_instance(dump_dir, { "includeSchemas": [test_schema], "includeUsers": [tested_user], "showProgress": False }), "Dump should not throw")
 
 shell.connect(__sandbox_uri2)
 
@@ -3770,7 +3793,7 @@ def alter_objects(s, schema):
 EXPECT_NO_THROWS(lambda: util.load_dump(dump_dir, { "dropExistingObjects": True, "resetProgress": True, "loadUsers": True, "showProgress": False }), "Load should not throw")
 
 EXPECT_STDOUT_CONTAINS("Checking for pre-existing objects...")
-EXPECT_STDOUT_CONTAINS(f"NOTE: Account '{test_user}'@'%' already exists, dropping...")
+EXPECT_STDOUT_CONTAINS(f"NOTE: Account '{tested_user}'@'%' already exists, dropping...")
 EXPECT_STDOUT_CONTAINS(f"NOTE: Schema `{test_schema}` already contains a table named `t`, dropping...")
 EXPECT_STDOUT_CONTAINS(f"NOTE: Schema `{test_schema}` already contains a view named `v`, dropping...")
 EXPECT_STDOUT_CONTAINS(f"NOTE: Schema `{test_schema}` already contains a trigger named `tt`, dropping...")
@@ -3866,7 +3889,7 @@ def alter_objects(s, schema):
 
 #@<> BUG#36561962 - cleanup
 session1.run_sql("DROP SCHEMA IF EXISTS !", [ test_schema ])
-session1.run_sql(f"DROP USER IF EXISTS {test_user}")
+session1.run_sql(f"DROP USER IF EXISTS {tested_user}")
 
 #@<> WL16731-TSFR_2_4_2 - dump without libraries, load into instance with an existing library {instance_supports_libraries}
 # constants
@@ -3915,6 +3938,59 @@ def alter_objects(s, schema):
 
 wipe_dir(dump_dir)
 
+#@<> BUG#37669785 - load would fail if user loading the dump doesn't have ALLOW_NONEXISTENT_DEFINER {VER(>=8.2.0)}
+# constants
+dump_dir = os.path.join(outdir, "bug_37669785")
+tested_schema = "tested_schema"
+tested_user = "'user'@'localhost'"
+
+# setup
+shell.connect(__sandbox_uri1)
+
+session.run_sql(f"DROP USER IF EXISTS {tested_user}")
+session.run_sql(f"CREATE USER {tested_user}")
+session.run_sql(f"GRANT ALL ON *.* TO {tested_user}")
+# this user needs to have the same privileges as the user loading the dump, otherwise load will fail when applying the grants
+session.run_sql(f"REVOKE SUPER, ALLOW_NONEXISTENT_DEFINER ON *.* FROM {tested_user}")
+
+session.run_sql("DROP SCHEMA IF EXISTS !", [tested_schema])
+session.run_sql("CREATE SCHEMA !", [tested_schema])
+session.run_sql("CREATE TABLE !.t (id INT)", [tested_schema])
+
+session.run_sql(f"CREATE DEFINER = {tested_user} PROCEDURE !.p() DETERMINISTIC BEGIN END", [tested_schema])
+session.run_sql(f"CREATE DEFINER = {tested_user} FUNCTION !.f() RETURNS INT DETERMINISTIC RETURN 1", [tested_schema])
+session.run_sql(f"CREATE DEFINER = {tested_user} TRIGGER !.tt AFTER DELETE ON !.t FOR EACH ROW BEGIN END", [tested_schema, tested_schema])
+session.run_sql(f"CREATE DEFINER = {tested_user} EVENT !.e ON SCHEDULE EVERY 1 YEAR DO BEGIN END", [tested_schema])
+session.run_sql(f"CREATE DEFINER = {tested_user} VIEW !.v AS SELECT * FROM !.t", [tested_schema, tested_schema])
+
+# dump
+shell.connect(__sandbox_uri1)
+EXPECT_NO_THROWS(lambda: util.dump_instance(dump_dir, { "includeSchemas": [tested_schema], "users": True, "excludeUsers": ["root"], "showProgress": False }), "Dump should not fail")
+
+# create the test user
+shell.connect(__sandbox_uri2)
+wipeout_server(session)
+
+session.run_sql(f"CREATE USER {test_user_account} IDENTIFIED BY ?", [test_user_pwd])
+session.run_sql(f"GRANT ALL ON *.* TO {test_user_account} WITH GRANT OPTION")
+session.run_sql(f"REVOKE SUPER, ALLOW_NONEXISTENT_DEFINER ON *.* FROM {test_user_account}")
+
+# this is required, otherwise load will fail when creating the function:
+# MySQL Error 1419 (HY000): You do not have the SUPER privilege and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable)
+session.run_sql("SET @@GLOBAL.log_bin_trust_function_creators = 1")
+
+# load
+shell.connect(test_user_uri(__mysql_sandbox_port2))
+EXPECT_NO_THROWS(lambda: util.load_dump(dump_dir, { "loadUsers": True, "showProgress": False }), "Load should not fail")
+
+#@<> BUG#37669785 - cleanup
+shell.connect(__sandbox_uri1)
+session.run_sql("DROP SCHEMA IF EXISTS !", [tested_schema])
+session.run_sql(f"DROP USER IF EXISTS {tested_user}")
+
+shell.connect(__sandbox_uri2)
+session.run_sql("SET @@GLOBAL.log_bin_trust_function_creators = 0")
+
 #@<> Cleanup
 testutil.destroy_sandbox(__mysql_sandbox_port1)
 testutil.destroy_sandbox(__mysql_sandbox_port2)
diff --git a/unittest/scripts/auto/py_shell/scripts/util_dump_instance_norecord.py b/unittest/scripts/auto/py_shell/scripts/util_dump_instance_norecord.py
index 1788620c1a..66ee5546aa 100644
--- a/unittest/scripts/auto/py_shell/scripts/util_dump_instance_norecord.py
+++ b/unittest/scripts/auto/py_shell/scripts/util_dump_instance_norecord.py
@@ -104,6 +104,7 @@
     "FLUSH_STATUS",
     "FLUSH_TABLES",
     "FLUSH_USER_RESOURCES",
+    "OPTION_TRACKER_OBSERVER",
     "REPLICATION_APPLIER",
     "ROLE_ADMIN",
     "SHOW_ROUTINE",
@@ -157,7 +158,9 @@
     "BINLOG_ENCRYPTION_ADMIN",
     "CLONE_ADMIN",
     "CONNECTION_ADMIN",
+    "CREATE_SPATIAL_REFERENCE_SYSTEM",
     "ENCRYPTION_KEY_ADMIN",
+    "EXPORT_QUERY_RESULTS",
     "FIREWALL_ADMIN",
     "FIREWALL_EXEMPT",
     "FIREWALL_USER",
@@ -173,6 +176,8 @@
     "MASKING_DICTIONARIES_ADMIN",
     "NDB_STORED_USER",
     "OPTIMIZE_LOCAL_TABLE",
+    "OPTION_TRACKER_OBSERVER",
+    "OPTION_TRACKER_UPDATER",
     "PASSWORDLESS_USER_ADMIN",
     "PERSIST_RO_VARIABLES_ADMIN",
     "REPLICATION_APPLIER",
@@ -470,11 +475,13 @@ def TEST_DUMP_AND_LOAD(schemas, options = {}):
     "server-id": str(random.randint(1, 4294967295)),
     "enforce_gtid_consistency": "ON",
     "gtid_mode": "ON",
+    "innodb_doublewrite": "OFF"
 })
 
 #@<> wait for server
 testutil.wait_sandbox_alive(uri)
 shell.connect(uri)
+session.run_sql("/*!80021 alter instance disable innodb redo_log */")
 
 #@<> WL13807-FR1.2 - an exception must be thrown if there is no open global session
 # WL13807-TSFR_1_16
@@ -930,8 +937,8 @@ def TEST_DUMP_AND_LOAD(schemas, options = {}):
 help_text = """
       - ociAuth: string (default: not set) - Use the specified authentication
         method when connecting to the OCI. Allowed values: api_key (used when
-        not explicitly set), instance_principal, resource_principal,
-        security_token.
+        not explicitly set), instance_obo_user, instance_principal,
+        resource_principal, security_token.
 """
 EXPECT_TRUE(help_text in util.help("dump_instance"))
 
@@ -954,7 +961,7 @@ def TEST_DUMP_AND_LOAD(schemas, options = {}):
 EXPECT_FAIL("ValueError", "Argument #2: The option 'ociProfile' cannot be used when the 'ociAuth' option is set to: resource_principal.", test_output_relative, { "osBucketName": "bucket", "ociAuth": "resource_principal", "ociProfile": "profile" })
 
 #@<> WL15884-TSFR_9_1 - `ociAuth` set to an invalid value
-EXPECT_FAIL("ValueError", "Argument #2: Invalid value of 'ociAuth' option, expected one of: api_key, instance_principal, resource_principal, security_token, but got: unknown.", test_output_relative, { "osBucketName": "bucket", "ociAuth": "unknown" })
+EXPECT_FAIL("ValueError", "Argument #2: Invalid value of 'ociAuth' option, expected one of: api_key, instance_obo_user, instance_principal, resource_principal, security_token, but got: unknown.", test_output_relative, { "osBucketName": "bucket", "ociAuth": "unknown" })
 
 #@<> WL13807: WL13804-FR5.8 - The `options` dictionary may contain a `defaultCharacterSet` key with a string value, which specifies the character set to be used during the dump. The session variables `character_set_client`, `character_set_connection`, and `character_set_results` must be set to this value for each opened connection.
 # WL13807-TSFR4_43
@@ -4435,11 +4442,26 @@ def setup_db():
 #@<> WL15947 - cleanup
 session.run_sql("DROP SCHEMA IF EXISTS !;", [schema_name])
 
-#@<> BUG#36701854 - dumps from a server with a greater minor version are disallowed {not __dbug_off}
+#@<> BUG#36701854 - dumps from a server with a greater minor version are allowed, but a warning is printed {VER(>=8.0.0) and not __dbug_off}
+testutil.dbug_set("+d,dumper_newer_server_version")
+
+newer_version = __mysh_version_no_extra.split(".")
+newer_version[1] = str(int(newer_version[1]) + 1)
+newer_version[2] = "0"
+newer_version = ".".join(newer_version)
+
+# trying to dump from an newer version results in a warning
+EXPECT_SUCCESS(None, test_output_absolute, { "dryRun": True, "showProgress": False })
+EXPECT_STDOUT_CONTAINS(f"WARNING: MySQL Server {newer_version} detected, which is newer than the MySQL Shell. Please upgrade the MySQL Shell if dump or load operation fails.")
+
+testutil.dbug_set("")
+
+#@<> BUG#37866205 - dumps from a server with a greater major version are disallowed {not __dbug_off}
 testutil.dbug_set("+d,dumper_unsupported_server_version")
 
 unsupported_version = __mysh_version_no_extra.split(".")
-unsupported_version[1] = str(int(unsupported_version[1]) + 1)
+unsupported_version[0] = str(int(unsupported_version[0]) + 1)
+unsupported_version[1] = "0"
 unsupported_version[2] = "0"
 unsupported_version = ".".join(unsupported_version)
 
diff --git a/unittest/scripts/auto/py_shell/scripts/util_dump_schemas_norecord.py b/unittest/scripts/auto/py_shell/scripts/util_dump_schemas_norecord.py
index c64e3290bd..e4bd2df8e7 100644
--- a/unittest/scripts/auto/py_shell/scripts/util_dump_schemas_norecord.py
+++ b/unittest/scripts/auto/py_shell/scripts/util_dump_schemas_norecord.py
@@ -244,11 +244,13 @@ def TEST_DUMP_AND_LOAD(schemas, options = {}):
     "log_bin": 1,
     "enforce_gtid_consistency": "ON",
     "gtid_mode": "ON",
+    "innodb_doublewrite": "OFF"
 })
 
 #@<> wait for server
 testutil.wait_sandbox_alive(uri)
 shell.connect(uri)
+session.run_sql("/*!80021 alter instance disable innodb redo_log */")
 
 #@<> WL13807-FR2.4 - an exception must be thrown if there is no open global session
 session.close()
@@ -702,8 +704,8 @@ def TEST_DUMP_AND_LOAD(schemas, options = {}):
 help_text = """
       - ociAuth: string (default: not set) - Use the specified authentication
         method when connecting to the OCI. Allowed values: api_key (used when
-        not explicitly set), instance_principal, resource_principal,
-        security_token.
+        not explicitly set), instance_obo_user, instance_principal,
+        resource_principal, security_token.
 """
 EXPECT_TRUE(help_text in util.help("dump_schemas"))
 
@@ -726,7 +728,7 @@ def TEST_DUMP_AND_LOAD(schemas, options = {}):
 EXPECT_FAIL("ValueError", "Argument #3: The option 'ociProfile' cannot be used when the 'ociAuth' option is set to: resource_principal.", [types_schema], test_output_relative, { "osBucketName": "bucket", "ociAuth": "resource_principal", "ociProfile": "profile" })
 
 #@<> WL15884-TSFR_9_1 - `ociAuth` set to an invalid value
-EXPECT_FAIL("ValueError", "Argument #3: Invalid value of 'ociAuth' option, expected one of: api_key, instance_principal, resource_principal, security_token, but got: unknown.", [types_schema], test_output_relative, { "osBucketName": "bucket", "ociAuth": "unknown" })
+EXPECT_FAIL("ValueError", "Argument #3: Invalid value of 'ociAuth' option, expected one of: api_key, instance_obo_user, instance_principal, resource_principal, security_token, but got: unknown.", [types_schema], test_output_relative, { "osBucketName": "bucket", "ociAuth": "unknown" })
 
 #@<> WL13807: WL13804-FR5.8 - The `options` dictionary may contain a `defaultCharacterSet` key with a string value, which specifies the character set to be used during the dump. The session variables `character_set_client`, `character_set_connection`, and `character_set_results` must be set to this value for each opened connection.
 # WL13807-TSFR4_43
@@ -3187,6 +3189,65 @@ def setup_db():
 #@<> BUG#36509026 - cleanup
 session.run_sql("DROP SCHEMA IF EXISTS !;", [schema_name])
 
+#@<> BUG#37892879 - error out when dumping with `create_invisible_pks` compatibility option and partitioned table doesn't have a primary key
+schema_name = "test_37892879"
+session.run_sql("DROP SCHEMA IF EXISTS !", [schema_name])
+session.run_sql("CREATE SCHEMA !", [schema_name])
+
+session.run_sql("""
+CREATE TABLE !.t1 (
+    col1 INT NOT NULL,
+    col2 DATE NOT NULL,
+    col3 INT NOT NULL,
+    col4 INT NOT NULL,
+    UNIQUE KEY (col1)
+)
+PARTITION BY HASH(col1)
+PARTITIONS 4;
+""", [schema_name])
+
+#@<> BUG#37892879 - test with ocimds:true - table is reported as invalid, as it does not contain a PK
+EXPECT_FAIL("Shell Error (52004)", "Compatibility issues were found", [ schema_name ], test_output_absolute, { "ocimds": True, "showProgress": False })
+EXPECT_STDOUT_CONTAINS(create_invisible_pks(schema_name, "t1").error())
+
+#@<> BUG#37892879 - test `create_invisible_pks` compatibility option - table is reported as invalid, as it's partitioned and does not contain a PK
+EXPECT_FAIL("Shell Error (52004)", "Compatibility issues were found", [ schema_name ], test_output_absolute, { "compatibility": [ "create_invisible_pks" ], "ocimds": True, "showProgress": False })
+EXPECT_STDOUT_CONTAINS(create_invisible_pks_partitioned_table(schema_name, "t1").error())
+
+#@<> BUG#37892879 - cleanup
+session.run_sql("DROP SCHEMA IF EXISTS !;", [schema_name])
+
+#@<> BUG#37904121 - disable off-loading to HeatWave
+# setup
+schema_name = "test_37904121"
+table_name = "t1"
+heatwave_table_name = "t2"
+supports_secondary_engine = __version_num >= 80013
+
+session.run_sql("DROP SCHEMA IF EXISTS !", [schema_name])
+session.run_sql("CREATE SCHEMA !", [schema_name])
+
+session.run_sql("CREATE TABLE !.! (a int primary key)", [schema_name, table_name])
+
+if supports_secondary_engine:
+    session.run_sql("CREATE TABLE !.! (a int primary key) SECONDARY_engine='raPID'", [schema_name, heatwave_table_name])
+
+old_log_sql = shell.options["logSql"]
+shell.options["logSql"] = "unfiltered"
+
+#@<> BUG#37904121 - test
+WIPE_SHELL_LOG()
+EXPECT_SUCCESS([ schema_name ], test_output_absolute, { "showProgress": False })
+EXPECT_SHELL_LOG_CONTAINS(f"SELECT {"SQL_NO_CACHE " if __version_num < 80000 else ""}`a` FROM `{schema_name}`.`{table_name}` ORDER BY `a` LIMIT 1")
+
+if supports_secondary_engine:
+    EXPECT_SHELL_LOG_CONTAINS(f"""SELECT /*+ SET_VAR(use_secondary_engine='OFF') {"SET_VAR(optimizer_switch='hypergraph_optimizer=OFF') " if __version_num >= 80400 and __version_num < 90000 else ""}*/ `a` FROM `{schema_name}`.`{heatwave_table_name}` ORDER BY `a` LIMIT 1""")
+
+#@<> BUG#37904121 - cleanup
+shell.options["logSql"] = old_log_sql
+
+session.run_sql("DROP SCHEMA IF EXISTS !;", [schema_name])
+
 #@<> Cleanup
 drop_all_schemas()
 session.run_sql("SET GLOBAL local_infile = false;")
diff --git a/unittest/scripts/auto/py_shell/scripts/util_dump_tables_norecord.py b/unittest/scripts/auto/py_shell/scripts/util_dump_tables_norecord.py
index 2c3cbabddc..9937e5a04c 100644
--- a/unittest/scripts/auto/py_shell/scripts/util_dump_tables_norecord.py
+++ b/unittest/scripts/auto/py_shell/scripts/util_dump_tables_norecord.py
@@ -261,11 +261,13 @@ def TEST_DUMP_AND_LOAD(schema, tables, options = {}):
     "log_bin": 1,
     "enforce_gtid_consistency": "ON",
     "gtid_mode": "ON",
+    "innodb_doublewrite": "OFF"
 })
 
 #@<> wait for server
 testutil.wait_sandbox_alive(uri)
 shell.connect(uri)
+session.run_sql("/*!80021 alter instance disable innodb redo_log */")
 
 #@<> WL13804-FR7.1 - If there is no open global Shell session, an exception must be thrown. (no open session)
 # WL13804-TSFR_7_1_1
@@ -653,8 +655,8 @@ def create_test_schema_objects():
 help_text = """
       - ociAuth: string (default: not set) - Use the specified authentication
         method when connecting to the OCI. Allowed values: api_key (used when
-        not explicitly set), instance_principal, resource_principal,
-        security_token.
+        not explicitly set), instance_obo_user, instance_principal,
+        resource_principal, security_token.
 """
 EXPECT_TRUE(help_text in util.help("dump_tables"))
 
@@ -677,7 +679,7 @@ def create_test_schema_objects():
 EXPECT_FAIL("ValueError", "Argument #4: The option 'ociProfile' cannot be used when the 'ociAuth' option is set to: resource_principal.", types_schema, types_schema_tables, test_output_relative, { "osBucketName": "bucket", "ociAuth": "resource_principal", "ociProfile": "profile" })
 
 #@<> WL15884-TSFR_9_1 - `ociAuth` set to an invalid value
-EXPECT_FAIL("ValueError", "Argument #4: Invalid value of 'ociAuth' option, expected one of: api_key, instance_principal, resource_principal, security_token, but got: unknown.", types_schema, types_schema_tables, test_output_relative, { "osBucketName": "bucket", "ociAuth": "unknown" })
+EXPECT_FAIL("ValueError", "Argument #4: Invalid value of 'ociAuth' option, expected one of: api_key, instance_obo_user, instance_principal, resource_principal, security_token, but got: unknown.", types_schema, types_schema_tables, test_output_relative, { "osBucketName": "bucket", "ociAuth": "unknown" })
 
 #@<> WL13804-FR5.8 - The `options` dictionary may contain a `defaultCharacterSet` key with a string value, which specifies the character set to be used during the dump. The session variables `character_set_client`, `character_set_connection`, and `character_set_results` must be set to this value for each opened connection.
 TEST_STRING_OPTION("defaultCharacterSet")
@@ -3496,6 +3498,25 @@ def setup_db():
 #@<> WL15947 - cleanup
 session.run_sql("DROP SCHEMA IF EXISTS !;", [schema_name])
 
+#@<> BUG#37770454 - shell crashes during dump if table has a functional key {VER(>=8.0.0)}
+tested_schema = "tested_schema"
+tested_table = "tested_table"
+
+session.run_sql("DROP SCHEMA IF EXISTS !;", [ tested_schema ])
+session.run_sql("CREATE SCHEMA !", [ tested_schema ])
+session.run_sql("""CREATE TABLE !.! (
+`id` bigint NOT NULL AUTO_INCREMENT,
+`b` bit(1) NOT NULL,
+`c` varchar(100) NOT NULL,
+PRIMARY KEY (`id`),
+UNIQUE KEY `a` (((case when (`b` <> 0) then `b` end)),`c`)
+)""", [ tested_schema, tested_table ])
+
+EXPECT_SUCCESS(tested_schema, [ tested_table ], test_output_absolute, { "showProgress": False })
+
+# cleanup
+session.run_sql("DROP SCHEMA !;", [ tested_schema ])
+
 #@<> Cleanup
 drop_all_schemas()
 session.run_sql("SET GLOBAL local_infile = false;")
diff --git a/unittest/scripts/auto/py_shell/scripts/util_export_table_norecord.py b/unittest/scripts/auto/py_shell/scripts/util_export_table_norecord.py
index fa21d757f1..05118a9a10 100644
--- a/unittest/scripts/auto/py_shell/scripts/util_export_table_norecord.py
+++ b/unittest/scripts/auto/py_shell/scripts/util_export_table_norecord.py
@@ -604,8 +604,8 @@ def get_magic_number(path, count):
 help_text = """
       - ociAuth: string (default: not set) - Use the specified authentication
         method when connecting to the OCI. Allowed values: api_key (used when
-        not explicitly set), instance_principal, resource_principal,
-        security_token.
+        not explicitly set), instance_obo_user, instance_principal,
+        resource_principal, security_token.
 """
 EXPECT_TRUE(help_text in util.help("export_table"))
 
@@ -628,7 +628,7 @@ def get_magic_number(path, count):
 EXPECT_FAIL("ValueError", "Argument #3: The option 'ociProfile' cannot be used when the 'ociAuth' option is set to: resource_principal.", quote(types_schema, types_schema_tables[0]), test_output_relative, { "osBucketName": "bucket", "ociAuth": "resource_principal", "ociProfile": "profile" })
 
 #@<> WL15884-TSFR_9_1 - `ociAuth` set to an invalid value
-EXPECT_FAIL("ValueError", "Argument #3: Invalid value of 'ociAuth' option, expected one of: api_key, instance_principal, resource_principal, security_token, but got: unknown.", quote(types_schema, types_schema_tables[0]), test_output_relative, { "osBucketName": "bucket", "ociAuth": "unknown" })
+EXPECT_FAIL("ValueError", "Argument #3: Invalid value of 'ociAuth' option, expected one of: api_key, instance_obo_user, instance_principal, resource_principal, security_token, but got: unknown.", quote(types_schema, types_schema_tables[0]), test_output_relative, { "osBucketName": "bucket", "ociAuth": "unknown" })
 
 #@<> WL13804-FR5.8 - The `options` dictionary may contain a `defaultCharacterSet` key with a string value, which specifies the character set to be used during the dump. The session variables `character_set_client`, `character_set_connection`, and `character_set_results` must be set to this value for each opened connection.
 TEST_STRING_OPTION("defaultCharacterSet")
diff --git a/unittest/scripts/auto/py_shell/scripts/util_import_table_norecord.py b/unittest/scripts/auto/py_shell/scripts/util_import_table_norecord.py
index 716dec6692..3ca522f4c3 100644
--- a/unittest/scripts/auto/py_shell/scripts/util_import_table_norecord.py
+++ b/unittest/scripts/auto/py_shell/scripts/util_import_table_norecord.py
@@ -127,11 +127,11 @@ def primary_key_prefix(table_name):
 prefix = primary_key_prefix(target_table)
 
 EXPECT_STDOUT_CONTAINS(f"{qualified_table_name()}: Records: 4079  Deleted: 0  Skipped: 4079  Warnings: 4079")
-EXPECT_STDOUT_CONTAINS(f"WARNING: world_x_cities.csv error 1062: Duplicate entry '1' for key '{prefix}PRIMARY'")
-EXPECT_STDOUT_CONTAINS(f"WARNING: world_x_cities.csv error 1062: Duplicate entry '2' for key '{prefix}PRIMARY'")
-EXPECT_STDOUT_CONTAINS(f"WARNING: world_x_cities.csv error 1062: Duplicate entry '3' for key '{prefix}PRIMARY'")
-EXPECT_STDOUT_CONTAINS(f"WARNING: world_x_cities.csv error 1062: Duplicate entry '4' for key '{prefix}PRIMARY'")
-EXPECT_STDOUT_CONTAINS(f"WARNING: world_x_cities.csv error 1062: Duplicate entry '5' for key '{prefix}PRIMARY'")
+EXPECT_STDOUT_CONTAINS(f"WARNING: LOAD DATA error for `wl12193`.`cities` in file 'world_x_cities.csv', error 1062: Duplicate entry '1' for key '{prefix}PRIMARY'")
+EXPECT_STDOUT_CONTAINS(f"WARNING: LOAD DATA error for `wl12193`.`cities` in file 'world_x_cities.csv', error 1062: Duplicate entry '2' for key '{prefix}PRIMARY'")
+EXPECT_STDOUT_CONTAINS(f"WARNING: LOAD DATA error for `wl12193`.`cities` in file 'world_x_cities.csv', error 1062: Duplicate entry '3' for key '{prefix}PRIMARY'")
+EXPECT_STDOUT_CONTAINS(f"WARNING: LOAD DATA error for `wl12193`.`cities` in file 'world_x_cities.csv', error 1062: Duplicate entry '4' for key '{prefix}PRIMARY'")
+EXPECT_STDOUT_CONTAINS(f"WARNING: LOAD DATA error for `wl12193`.`cities` in file 'world_x_cities.csv', error 1062: Duplicate entry '5' for key '{prefix}PRIMARY'")
 EXPECT_STDOUT_CONTAINS(f"File '{filename_for_output(world_x_cities_csv)}' (250.53 KB) was imported in ")
 EXPECT_STDOUT_CONTAINS(f"Total rows affected in {qualified_table_name()}: Records: 4079  Deleted: 0  Skipped: 4079  Warnings: 4079")
 EXPECT_EQ(target_table_checksum, md5_table(session, target_schema, target_table))
@@ -188,11 +188,11 @@ def primary_key_prefix(table_name):
 prefix = primary_key_prefix("document_store")
 
 EXPECT_STDOUT_CONTAINS(f"{target_schema}.document_store: Records: 25359  Deleted: 0  Skipped: 25359  Warnings: 25359")
-EXPECT_STDOUT_CONTAINS(f"WARNING: primer-dataset-id.json error 1062: Duplicate entry '000000000001' for key '{prefix}PRIMARY'")
-EXPECT_STDOUT_CONTAINS(f"WARNING: primer-dataset-id.json error 1062: Duplicate entry '000000000002' for key '{prefix}PRIMARY'")
-EXPECT_STDOUT_CONTAINS(f"WARNING: primer-dataset-id.json error 1062: Duplicate entry '000000000003' for key '{prefix}PRIMARY'")
-EXPECT_STDOUT_CONTAINS(f"WARNING: primer-dataset-id.json error 1062: Duplicate entry '000000000004' for key '{prefix}PRIMARY'")
-EXPECT_STDOUT_CONTAINS(f"WARNING: primer-dataset-id.json error 1062: Duplicate entry '000000000005' for key '{prefix}PRIMARY'")
+EXPECT_STDOUT_CONTAINS(f"WARNING: LOAD DATA error for `wl12193`.`document_store` in file 'primer-dataset-id.json', error 1062: Duplicate entry '000000000001' for key '{prefix}PRIMARY'")
+EXPECT_STDOUT_CONTAINS(f"WARNING: LOAD DATA error for `wl12193`.`document_store` in file 'primer-dataset-id.json', error 1062: Duplicate entry '000000000002' for key '{prefix}PRIMARY'")
+EXPECT_STDOUT_CONTAINS(f"WARNING: LOAD DATA error for `wl12193`.`document_store` in file 'primer-dataset-id.json', error 1062: Duplicate entry '000000000003' for key '{prefix}PRIMARY'")
+EXPECT_STDOUT_CONTAINS(f"WARNING: LOAD DATA error for `wl12193`.`document_store` in file 'primer-dataset-id.json', error 1062: Duplicate entry '000000000004' for key '{prefix}PRIMARY'")
+EXPECT_STDOUT_CONTAINS(f"WARNING: LOAD DATA error for `wl12193`.`document_store` in file 'primer-dataset-id.json', error 1062: Duplicate entry '000000000005' for key '{prefix}PRIMARY'")
 EXPECT_STDOUT_CONTAINS(f"File '{filename_for_output(primer_dataset_id)}' (11.29 MB) was imported in ")
 EXPECT_STDOUT_CONTAINS(f"Total rows affected in {target_schema}.document_store: Records: 25359  Deleted: 0  Skipped: 25359  Warnings: 25359")
 
@@ -350,8 +350,8 @@ def TEST_STRING_OPTION(option):
 help_text = """
       - ociAuth: string (default: not set) - Use the specified authentication
         method when connecting to the OCI. Allowed values: api_key (used when
-        not explicitly set), instance_principal, resource_principal,
-        security_token.
+        not explicitly set), instance_obo_user, instance_principal,
+        resource_principal, security_token.
 """
 EXPECT_TRUE(help_text in util.help("import_table"))
 
@@ -375,7 +375,7 @@ def TEST_STRING_OPTION(option):
 EXPECT_THROWS(lambda: util.import_table(world_x_cities_dump, { "osBucketName": "bucket", "ociAuth": "resource_principal", "ociProfile": "profile", "schema": target_schema, "table": target_table }), "ValueError: Argument #2: The option 'ociProfile' cannot be used when the 'ociAuth' option is set to: resource_principal.")
 
 #@<> WL15884-TSFR_9_1 - `ociAuth` set to an invalid value
-EXPECT_THROWS(lambda: util.import_table(world_x_cities_dump, { "osBucketName": "bucket", "ociAuth": "unknown", "schema": target_schema, "table": target_table }), "ValueError: Argument #2: Invalid value of 'ociAuth' option, expected one of: api_key, instance_principal, resource_principal, security_token, but got: unknown.")
+EXPECT_THROWS(lambda: util.import_table(world_x_cities_dump, { "osBucketName": "bucket", "ociAuth": "unknown", "schema": target_schema, "table": target_table }), "ValueError: Argument #2: Invalid value of 'ociAuth' option, expected one of: api_key, instance_obo_user, instance_principal, resource_principal, security_token, but got: unknown.")
 
 #@<> WL14387-TSFR_1_1_1 - s3BucketName - string option
 TEST_STRING_OPTION("s3BucketName")
diff --git a/unittest/scripts/auto/py_shell/scripts/util_load_dump_trx_norecord.py b/unittest/scripts/auto/py_shell/scripts/util_load_dump_trx_norecord.py
index 06cd38d824..2e94173f44 100644
--- a/unittest/scripts/auto/py_shell/scripts/util_load_dump_trx_norecord.py
+++ b/unittest/scripts/auto/py_shell/scripts/util_load_dump_trx_norecord.py
@@ -61,7 +61,7 @@ def hack_in_bytes_per_chunk(dump, bytes_per_chunk):
   pass
 testutil.mkdir(__tmp_dir+"/ldtest")
 
-testutil.deploy_sandbox(__mysql_sandbox_port1, "root")
+testutil.deploy_sandbox(__mysql_sandbox_port1, "root", {"innodb_doublewrite":"OFF"})
 
 shell.connect(__sandbox_uri1)
 
diff --git a/unittest/scripts/auto/py_shell/validation/mysql_classic_session_help_norecord.py b/unittest/scripts/auto/py_shell/validation/mysql_classic_session_help_norecord.py
index 84535c6b9d..3c14de08e5 100644
--- a/unittest/scripts/auto/py_shell/validation/mysql_classic_session_help_norecord.py
+++ b/unittest/scripts/auto/py_shell/validation/mysql_classic_session_help_norecord.py
@@ -28,9 +28,15 @@
             Commits all the operations executed after a call to
             start_transaction().
 
+      get_client_data(key)
+            Returns value associated with the session for the given key.
+
       get_connection_id()
             Retrieves the connection id for the current session.
 
+      get_sql_mode()
+            Retrieves the SQL_MODE for the current session.
+
       get_ssh_uri()
             Retrieves the SSH URI for the current session.
 
@@ -51,6 +57,9 @@
             Executes a query and returns the corresponding ClassicResult
             object.
 
+      set_client_data(key, value)
+            Associates a value with the session for the given key.
+
       set_query_attributes()
             Defines query attributes that apply to the next statement sent to
             the server for execution.
@@ -58,6 +67,9 @@
       start_transaction()
             Starts a transaction context on the server.
 
+      track_system_variable(variable)
+            Enables session tracking of the given system variable.
+
 #@<OUT> session.close
 NAME
       close - Closes the internal connection to the MySQL Server held on this
@@ -201,3 +213,49 @@
          session.run_sql('INSTALL COMPONENT
       "file://component_query_attributes"');
 
+#@<OUT> Help on get_sql_mode()
+NAME
+      get_sql_mode - Retrieves the SQL_MODE for the current session.
+
+SYNTAX
+      <ClassicSession>.get_sql_mode()
+
+RETURNS
+      Value of the SQL_MODE session variable.
+
+DESCRIPTION
+      Queries the value of the SQL_MODE session variable. If session tracking
+      of SQL_MODE is enabled, it will fetch its cached value.
+
+#@<OUT> Help on set_client_data()
+NAME
+      set_client_data - Associates a value with the session for the given key.
+
+SYNTAX
+      <ClassicSession>.set_client_data(key, value)
+
+WHERE
+      key: (string) A string to identify the stored value.
+      value: JSON-like value to be stored.
+
+DESCRIPTION
+      Saves a value in the session data structure associated to the given key.
+      The value can be retrieved later using get_client_data().
+
+#@<OUT> Help on get_client_data()
+NAME
+      get_client_data - Returns value associated with the session for the given
+                        key.
+
+SYNTAX
+      <ClassicSession>.get_client_data(key)
+
+WHERE
+      key: (string) A string to identify the stored value.
+
+RETURNS
+      JSON-like value stored for the key.
+
+DESCRIPTION
+      Returns a value previously stored in the session data structure
+      associated with set_client_data().
diff --git a/unittest/scripts/auto/py_shell/validation/mysql_help_norecord.py b/unittest/scripts/auto/py_shell/validation/mysql_help_norecord.py
index b0fe026da0..873cffc580 100644
--- a/unittest/scripts/auto/py_shell/validation/mysql_help_norecord.py
+++ b/unittest/scripts/auto/py_shell/validation/mysql_help_norecord.py
@@ -43,6 +43,9 @@
       split_script(script)
             Split a SQL script into individual statements.
 
+      tokenize_statement(statement)
+            Lexes a MySQL statement into a list of tokens.
+
       unquote_identifier(s)
             Unquote a MySQL identifier.
 
diff --git a/unittest/scripts/auto/py_shell/validation/options_help_norecord.py b/unittest/scripts/auto/py_shell/validation/options_help_norecord.py
index 686bf71d94..ad469df2a9 100644
--- a/unittest/scripts/auto/py_shell/validation/options_help_norecord.py
+++ b/unittest/scripts/auto/py_shell/validation/options_help_norecord.py
@@ -30,10 +30,6 @@
         clusterset, or adding an instance to one
       - dba.gtidWaitTimeout: timeout value in seconds to wait for GTIDs to be
         synchronized
-      - dba.logSql: 0..2, log SQL statements executed by AdminAPI operations: 0
-        - logging disabled; 1 - log statements other than SELECT and SHOW; 2 -
-        log all statements. Option takes precedence over --log-sql in Dba.*
-        context, if enabled.
       - dba.restartWaitTimeout: timeout in seconds to wait for MySQL server to
         come back after a restart during clone recovery
       - dba.versionCompatibilityChecks: bool, checks version compatibility for
@@ -78,7 +74,6 @@
         file
       - oci.profile: Specify which section in oci.configFile will be used as
         profile settings
-      - outputFormat: Deprecated, use resultFormat instead
       - pager: string which specifies the external command which is going to be
         used to display the paged output
       - passwordsFromStdin: boolean value that indicates if the shell should
diff --git a/unittest/scripts/auto/py_shell/validation/shell_help_norecord.py b/unittest/scripts/auto/py_shell/validation/shell_help_norecord.py
index 03079c70a9..d171f00ae9 100644
--- a/unittest/scripts/auto/py_shell/validation/shell_help_norecord.py
+++ b/unittest/scripts/auto/py_shell/validation/shell_help_norecord.py
@@ -40,9 +40,15 @@
       delete_all_credentials()
             Deletes all credentials managed by the configured helper.
 
+      delete_all_secrets()
+            Deletes all secrets managed by the configured helper.
+
       delete_credential(url)
             Deletes credential for the given URL using the configured helper.
 
+      delete_secret(key)
+            Deletes secret for the given key using the configured helper.
+
       disable_pager()
             Disables pager for the current scripting mode.
 
@@ -69,6 +75,10 @@
       list_credentials()
             Retrieves a list of all URLs stored by the configured helper.
 
+      list_secrets()
+            Retrieves a list of all secrets' keys stored by the configured
+            helper.
+
       list_sql_handlers()
             Lists the name and description of any registered SQL handlers.
 
@@ -87,6 +97,9 @@
       prompt(message[, options])
             Utility function to prompt data from the user.
 
+      read_secret(key)
+            Reads secret for the given key using the configured helper.
+
       reconnect()
             Reconnect the global session.
 
@@ -112,6 +125,9 @@
       store_credential(url[, password])
             Stores given credential using the configured helper.
 
+      store_secret(key[, value])
+            Stores given secret using the configured helper.
+
       unparse_uri(options)
             Formats the given connection options to a URI string suitable for
             mysqlsh.
@@ -480,6 +496,14 @@ class name for the built in shell global object:
       - if configured credential helper is invalid.
       - if deleting the credentials fails.
 
+#@<OUT> shell.delete_all_secrets
+NAME
+      delete_all_secrets - Deletes all secrets managed by the configured
+                           helper.
+
+SYNTAX
+      shell.delete_all_secrets()
+
 #@<OUT> shell.delete_credential
 NAME
       delete_credential - Deletes credential for the given URL using the
@@ -502,6 +526,17 @@ class name for the built in shell global object:
       - if configured credential helper is invalid.
       - if deleting the credential fails.
 
+#@<OUT> shell.delete_secret
+NAME
+      delete_secret - Deletes secret for the given key using the configured
+                      helper.
+
+SYNTAX
+      shell.delete_secret(key)
+
+WHERE
+      key: A key of the secret to delete.
+
 #@<OUT> shell.disable_pager
 NAME
       disable_pager - Disables pager for the current scripting mode.
@@ -579,7 +614,17 @@ class name for the built in shell global object:
       The special values "default" and "<disabled>" are not on the list.
 
       Only values on this list (plus "default" and "<disabled>") can be used to
-      set the "credentialStore.helper" option.
+      set the "credentialStore.helper" Shell option.
+
+      Limitations:
+
+      - The login-path helper cannot store secrets longer than 78 characters.
+      - The login-path helper cannot store the following characters: '\0',
+        '0x03', '0x04', '\n', '\r', '0x0F', '0x11', '0x12', '0x13', '0x15',
+        '0x16', '0x17', '0x19', '0x1A', '0x1C', '0x7F'.
+      - The keychain helper cannot store the following characters: '\0',
+        '\n'.
+      - The secret-service helper can only store UTF-8 encoded strings.
 
 #@<OUT> shell.list_credentials
 NAME
@@ -598,6 +643,14 @@ class name for the built in shell global object:
       - if configured credential helper is invalid.
       - if listing the URLs fails.
 
+#@<OUT> shell.list_secrets
+NAME
+      list_secrets - Retrieves a list of all secrets' keys stored by the
+                     configured helper.
+
+SYNTAX
+      shell.list_secrets()
+
 #@<OUT> shell.list_ssh_connections
 NAME
       list_ssh_connections - Retrieves a list of all active SSH tunnels.
@@ -655,10 +708,6 @@ class name for the built in shell global object:
         clusterset, or adding an instance to one
       - dba.gtidWaitTimeout: timeout value in seconds to wait for GTIDs to be
         synchronized
-      - dba.logSql: 0..2, log SQL statements executed by AdminAPI operations: 0
-        - logging disabled; 1 - log statements other than SELECT and SHOW; 2 -
-        log all statements. Option takes precedence over --log-sql in Dba.*
-        context, if enabled.
       - dba.restartWaitTimeout: timeout in seconds to wait for MySQL server to
         come back after a restart during clone recovery
       - dba.versionCompatibilityChecks: bool, checks version compatibility for
@@ -703,7 +752,6 @@ class name for the built in shell global object:
         file
       - oci.profile: Specify which section in oci.configFile will be used as
         profile settings
-      - outputFormat: Deprecated, use resultFormat instead
       - pager: string which specifies the external command which is going to be
         used to display the paged output
       - passwordsFromStdin: boolean value that indicates if the shell should
@@ -909,6 +957,19 @@ class name for the built in shell global object:
 SYNTAX
       shell.disconnect()
 
+#@<OUT> shell.read_secret
+NAME
+      read_secret - Reads secret for the given key using the configured helper.
+
+SYNTAX
+      shell.read_secret(key)
+
+WHERE
+      key: A key of the secret to read.
+
+RETURNS
+      Secret associated with the given key.
+
 #@<OUT> shell.reconnect
 NAME
       reconnect - Reconnect the global session.
@@ -1266,6 +1327,28 @@ class name for the built in shell global object:
       - if configured credential helper is invalid.
       - if storing the credential fails.
 
+#@<OUT> shell.store_secret
+NAME
+      store_secret - Stores given secret using the configured helper.
+
+SYNTAX
+      shell.store_secret(key[, value])
+
+WHERE
+      key: A key that uniquely identifies the secret.
+      value: Value for the given key.
+
+DESCRIPTION
+      If value is not provided, displays a prompt to enter the secret.
+
+      If secret with the given key is already in the storage, its value is
+      overwritten.
+
+      The current helper is set by the credentialStore.helper Shell option.
+
+      The limitations of helpers on allowed characters do not apply to secrets
+      stored by store_secret().
+
 #@<OUT> shell.create_context
 NAME
       create_context - Create a shell context wrapper used in multiuser
diff --git a/unittest/scripts/auto/py_shell/validation/util_help_norecord.py b/unittest/scripts/auto/py_shell/validation/util_help_norecord.py
index 0b241dd7c0..3efb0496b3 100644
--- a/unittest/scripts/auto/py_shell/validation/util_help_norecord.py
+++ b/unittest/scripts/auto/py_shell/validation/util_help_norecord.py
@@ -10,6 +10,11 @@
 PROPERTIES
       debug
             Debugging and diagnostic utilities.
+?{__has_util_plugin}
+
+      visual
+            Visualization utilities.
+?{}
 
 FUNCTIONS
       check_for_server_upgrade([connectionData][, options])
@@ -642,8 +647,8 @@
         instead of the default one.
       - ociAuth: string (default: not set) - Use the specified authentication
         method when connecting to the OCI. Allowed values: api_key (used when
-        not explicitly set), instance_principal, resource_principal,
-        security_token.
+        not explicitly set), instance_obo_user, instance_principal,
+        resource_principal, security_token.
       - s3BucketName: string (default: not set) - Name of the AWS S3 bucket to
         use. The bucket must already exist.
       - s3CredentialsFile: string (default: not set) - Use the specified AWS
@@ -858,8 +863,8 @@
         instead of the default one.
       - ociAuth: string (default: not set) - Use the specified authentication
         method when connecting to the OCI. Allowed values: api_key (used when
-        not explicitly set), instance_principal, resource_principal,
-        security_token.
+        not explicitly set), instance_obo_user, instance_principal,
+        resource_principal, security_token.
       - s3BucketName: string (default: not set) - Name of the AWS S3 bucket to
         use. The bucket must already exist.
       - s3CredentialsFile: string (default: not set) - Use the specified AWS
@@ -1271,8 +1276,8 @@
         instead of the default one.
       - ociAuth: string (default: not set) - Use the specified authentication
         method when connecting to the OCI. Allowed values: api_key (used when
-        not explicitly set), instance_principal, resource_principal,
-        security_token.
+        not explicitly set), instance_obo_user, instance_principal,
+        resource_principal, security_token.
       - s3BucketName: string (default: not set) - Name of the AWS S3 bucket to
         use. The bucket must already exist.
       - s3CredentialsFile: string (default: not set) - Use the specified AWS
@@ -1655,8 +1660,8 @@
         instead of the default one.
       - ociAuth: string (default: not set) - Use the specified authentication
         method when connecting to the OCI. Allowed values: api_key (used when
-        not explicitly set), instance_principal, resource_principal,
-        security_token.
+        not explicitly set), instance_obo_user, instance_principal,
+        resource_principal, security_token.
       - s3BucketName: string (default: not set) - Name of the AWS S3 bucket to
         use. The bucket must already exist.
       - s3CredentialsFile: string (default: not set) - Use the specified AWS
@@ -1995,8 +2000,8 @@
         instead of the default one.
       - ociAuth: string (default: not set) - Use the specified authentication
         method when connecting to the OCI. Allowed values: api_key (used when
-        not explicitly set), instance_principal, resource_principal,
-        security_token.
+        not explicitly set), instance_obo_user, instance_principal,
+        resource_principal, security_token.
       - s3BucketName: string (default: not set) - Name of the AWS S3 bucket to
         use. The bucket must already exist.
       - s3CredentialsFile: string (default: not set) - Use the specified AWS
@@ -2258,8 +2263,8 @@
         instead of the default one.
       - ociAuth: string (default: not set) - Use the specified authentication
         method when connecting to the OCI. Allowed values: api_key (used when
-        not explicitly set), instance_principal, resource_principal,
-        security_token.
+        not explicitly set), instance_obo_user, instance_principal,
+        resource_principal, security_token.
       - s3BucketName: string (default: not set) - Name of the AWS S3 bucket to
         use. The bucket must already exist.
       - s3CredentialsFile: string (default: not set) - Use the specified AWS
@@ -2379,8 +2384,8 @@
         instead of the default one.
       - ociAuth: string (default: not set) - Use the specified authentication
         method when connecting to the OCI. Allowed values: api_key (used when
-        not explicitly set), instance_principal, resource_principal,
-        security_token.
+        not explicitly set), instance_obo_user, instance_principal,
+        resource_principal, security_token.
       - s3BucketName: string (default: not set) - Name of the AWS S3 bucket to
         use. The bucket must already exist.
       - s3CredentialsFile: string (default: not set) - Use the specified AWS
@@ -2639,8 +2644,8 @@
         instead of the default one.
       - ociAuth: string (default: not set) - Use the specified authentication
         method when connecting to the OCI. Allowed values: api_key (used when
-        not explicitly set), instance_principal, resource_principal,
-        security_token.
+        not explicitly set), instance_obo_user, instance_principal,
+        resource_principal, security_token.
       - s3BucketName: string (default: not set) - Name of the AWS S3 bucket to
         use. The bucket must already exist.
       - s3CredentialsFile: string (default: not set) - Use the specified AWS
@@ -2734,6 +2739,10 @@
       without a Primary Key - Slow queries (if enabled) - Performance Schema
       configuration
 
+      When the `hostInfo` option is enabled, various other OS level information
+      from the host is collected using various diagnostic utilities. In Linux
+      and macos, sudo is used to execute some of them.
+
       Replication/InnoDB Cluster - mysql_innodb_cluster_metadata schema and
       contents - Replication related tables in performance_schema - Replication
       status information - InnoDB Cluster accounts and their grants - InnoDB
@@ -2759,6 +2768,8 @@
         slow_log to be enabled and configured for TABLE output. Default false.
       - ignoreErrors: Bool - If true, ignores query errors during collection.
         Default false.
+      - hostInfo: Bool - If true and the target server is localhost, collects
+        various host diagnostics info. Default true.
       - customSql: Array - Custom list of SQL statements to execute.
       - customShell: Array - Custom list of shell commands to execute.
 
@@ -2786,6 +2797,10 @@
       without a Primary Key - Slow queries (if enabled) - Performance Schema
       configuration
 
+      When the `hostInfo` option is enabled, various other OS level information
+      from the host is collected using various diagnostic utilities. In Linux
+      and macos, sudo is used to execute some of them.
+
       Replication/InnoDB Cluster - mysql_innodb_cluster_metadata schema and
       contents - Replication related tables in performance_schema - Replication
       status information - InnoDB Cluster accounts and their grants - InnoDB
@@ -2811,6 +2826,8 @@
         slow_log to be enabled and configured for TABLE output. Default false.
       - ignoreErrors: Bool - If true, ignores query errors during collection.
         Default false.
+      - hostInfo: Bool - If true and the target server is localhost, collects
+        various host diagnostics info. Default true.
       - customSql: Array - Custom list of SQL statements to execute.
       - customShell: Array - Custom list of shell commands to execute.
 
@@ -2838,6 +2855,10 @@
       BENCHMARK()) - Process list, open tables, host cache - System info
       collected from the OS (if connected to localhost)
 
+      When the `hostInfo` option is enabled, various other OS level information
+      from the host is collected using various diagnostic utilities. In Linux
+      and macos, sudo is used to execute some of them.
+
       Schema Statistics - Number of schema objects (sys.schema_table_overview)
       - Full list of tables with basic stats - Top 20 biggest tables with
       index, blobs and partitioning information - Stored procedure and function
@@ -2869,6 +2890,8 @@
       - pfsInstrumentation: String - One of current, medium, full. Controls
         whether additional PERFORMANCE_SCHEMA instruments and consumers are
         temporarily enabled (default 'current').
+      - hostInfo: Bool - If true and the target server is localhost, collects
+        various host diagnostics info. Default true.
       - customSql: Array - Custom list of SQL statements to execute. If the
         statement is prefixed with `before:` or nothing, it will be executed
         once, before the metrics collection loop. If prefixed with `after:`, it
@@ -2914,6 +2937,10 @@
       additional instrumentation may provide additional insights, at the cost
       of an larger impact on server performance.
 
+      When the `hostInfo` option is enabled, various other OS level information
+      from the host is collected using various diagnostic utilities. In Linux
+      and macos, sudo is used to execute some of them.
+
       The options parameter accepts the following options:
 
       - delay: Integer - Number of seconds to wait between collection
@@ -2924,6 +2951,8 @@
       - pfsInstrumentation: String - One of current, medium, full. Controls
         whether additional PERFORMANCE_SCHEMA instruments and consumers are
         temporarily enabled (default 'current').
+      - hostInfo: Bool - If true and the target server is localhost, collects
+        various host diagnostics info. Default true.
       - customSql: Array - Custom list of SQL statements to execute. If the
         statement is prefixed with `before:` or nothing, it will be executed
         once, before the metrics collection loop. If prefixed with `after:`, it
@@ -2949,7 +2978,8 @@
   of the default one.
 - ociAuth: string (default: not set) - Use the specified authentication method
   when connecting to the OCI. Allowed values: api_key (used when not explicitly
-  set), instance_principal, resource_principal, security_token.
+  set), instance_obo_user, instance_principal, resource_principal,
+  security_token.
 
 Description
 
@@ -2967,6 +2997,7 @@
 connecting to the OCI:
 
 - api_key - API Key-Based Authentication
+- instance_obo_user - Instance Principal Authentication with delegation token
 - instance_principal - Instance Principal Authentication
 - resource_principal - Resource Principal Authentication
 - security_token - Session Token-Based Authentication
diff --git a/unittest/scripts/js_devapi/scripts/mysql_module.js b/unittest/scripts/js_devapi/scripts/mysql_module.js
index 47fc55f746..0bfad377e1 100644
--- a/unittest/scripts/js_devapi/scripts/mysql_module.js
+++ b/unittest/scripts/js_devapi/scripts/mysql_module.js
@@ -17,6 +17,7 @@ validateMembers(mysql, [
     'getSession',
     'help',
     'parseStatementAst',
+    'tokenizeStatement',
     'quoteIdentifier',
     'splitScript',
     'unquoteIdentifier'])
@@ -45,6 +46,16 @@ mysql.parseStatementAst("this is not valid sql");
 mysql.parseStatementAst("");
 mysql.parseStatementAst("SELECT");
 
+//@ tokenizeStatement
+mysql.tokenizeStatement("select * from /* comment */ foo.bar /* `\"'` '*/")
+
+//@# tokenizeStatement errors
+mysql.tokenizeStatement("/*")
+mysql.tokenizeStatement("'")
+mysql.tokenizeStatement("`")
+mysql.tokenizeStatement('"')
+mysql.tokenizeStatement('\\')
+
 //@<> BUG#37018247 - quoted literals when ANSI quotes is disabled - TODO
 EXPECT_NO_THROWS(function () { mysql.parseStatementAst('SELECT "1"'); });
 
diff --git a/unittest/scripts/js_devapi/scripts/mysql_session.js b/unittest/scripts/js_devapi/scripts/mysql_session.js
index a7250a5b23..a12e982fd8 100644
--- a/unittest/scripts/js_devapi/scripts/mysql_session.js
+++ b/unittest/scripts/js_devapi/scripts/mysql_session.js
@@ -8,17 +8,21 @@ var classicSession = mysql.getClassicSession(__uripwd);
 validateMembers(classicSession, [
     'close',
     'commit',
+    'getClientData',
     'getConnectionId',
     'getUri',
+    'getSqlMode',
     'getSshUri',
     'help',
     'isOpen',
     'startTransaction',
+    'setClientData',
     'setQueryAttributes',
     'rollback',
     'runSql',
     'connectionId',
     'sshUri',
+    'trackSystemVariable',
     'uri',
     '_getSocketFd'])
 
@@ -131,5 +135,8 @@ classicSession.runSql("select ?, ?", ['hello', 1234]);
 //@<OUT> ClassicSession: runSql with various parameter types
 classicSession.runSql('select ?,?,?,?,?', [null, 1234, -0.12345, 3.14159265359, 'hellooooo']).fetchOne();
 
+//@<> ClassicSession: runSql with ! placeholders
+EXPECT_EQ(__user, classicSession.runSql('select user from !.! where user=?', ['mysql', 'user', __user]).fetchOne()[0])
+
 // Cleanup
 classicSession.close();
diff --git a/unittest/scripts/js_devapi/scripts/mysqlx_session.js b/unittest/scripts/js_devapi/scripts/mysqlx_session.js
index 45c4aeb081..7734a94629 100644
--- a/unittest/scripts/js_devapi/scripts/mysqlx_session.js
+++ b/unittest/scripts/js_devapi/scripts/mysqlx_session.js
@@ -10,11 +10,13 @@ validateMembers(mySession, [
     'commit',
     'createSchema',
     'dropSchema',
+    'getClientData',
     'getConnectionId',
     'getCurrentSchema',
     'getDefaultSchema',
     'getSchema',
     'getSchemas',
+    'getSqlMode',
     'getSshUri',
     'getUri',
     'help',
@@ -30,6 +32,7 @@ validateMembers(mySession, [
     'defaultSchema',
     'uri',
     'currentSchema',
+    'setClientData',
     'setSavepoint',
     'sshUri',
     'releaseSavepoint',
diff --git a/unittest/scripts/js_devapi/scripts/mysqlx_session_sql.js b/unittest/scripts/js_devapi/scripts/mysqlx_session_sql.js
index 58735cc9c6..c9c0fac1d4 100644
--- a/unittest/scripts/js_devapi/scripts/mysqlx_session_sql.js
+++ b/unittest/scripts/js_devapi/scripts/mysqlx_session_sql.js
@@ -43,9 +43,11 @@ session.sql('SELECT * FROM wl12813 WHERE doc->>\'$."notnested.like"\' = ?').exec
 session.runSql('SELECT * FROM wl12813 WHERE doc->>\'$."notnested.like"\' = ?')
 
 //@ runSql with various parameter types
-// NOTE: xplugin has a bug where double values lose precision
 session.runSql('select ?,?,?,?,?', [null, 1234, -0.12345, 3.14159265359, 'hellooooo']).fetchOne();
 
+//@ BUG#34715428: runSql with ! placeholders
+session.runSql('select user from !.! where user=?', ['mysql', 'user', __user]).fetchOne()[0]
+
 //@<> Finalizing
 session.dropSchema('session_sql');
 session.close();
diff --git a/unittest/scripts/js_devapi/validation/mysql_module.js b/unittest/scripts/js_devapi/validation/mysql_module.js
index cca9c0f939..eb31493fee 100644
--- a/unittest/scripts/js_devapi/validation/mysql_module.js
+++ b/unittest/scripts/js_devapi/validation/mysql_module.js
@@ -19,6 +19,68 @@ Argument #1 is expected to be a string (TypeError)
 ||mismatched input 'this' expecting {<EOF>, '(', ALTER_SYMBOL, ANALYZE_SYMBOL, BEGIN_SYMBOL, BINLOG_SYMBOL, CACHE_SYMBOL, CALL_SYMBOL, CHANGE_SYMBOL, CHECKSUM_SYMBOL, CHECK_SYMBOL, COMMIT_SYMBOL, CREATE_SYMBOL, DEALLOCATE_SYMBOL, DELETE_SYMBOL, DESC_SYMBOL, DESCRIBE_SYMBOL, DO_SYMBOL, DROP_SYMBOL, EXECUTE_SYMBOL, EXPLAIN_SYMBOL, FLUSH_SYMBOL, GET_SYMBOL, GRANT_SYMBOL, HANDLER_SYMBOL, HELP_SYMBOL, IMPORT_SYMBOL, INSERT_SYMBOL, INSTALL_SYMBOL, KILL_SYMBOL, LOAD_SYMBOL, LOCK_SYMBOL, OPTIMIZE_SYMBOL, PREPARE_SYMBOL, PURGE_SYMBOL, RELEASE_SYMBOL, RENAME_SYMBOL, REPAIR_SYMBOL, REPLACE_SYMBOL, RESET_SYMBOL, RESIGNAL_SYMBOL, REVOKE_SYMBOL, ROLLBACK_SYMBOL, SAVEPOINT_SYMBOL, SELECT_SYMBOL, SET_SYMBOL, SHOW_SYMBOL, SHUTDOWN_SYMBOL, SIGNAL_SYMBOL, START_SYMBOL, STOP_SYMBOL, TABLE_SYMBOL, TRUNCATE_SYMBOL, UNINSTALL_SYMBOL, UNLOCK_SYMBOL, UPDATE_SYMBOL, USE_SYMBOL, VALUES_SYMBOL, WITH_SYMBOL, XA_SYMBOL, CLONE_SYMBOL, RESTART_SYMBOL} (RuntimeError)
 ||no viable alternative at input 'SELECT' (RuntimeError)
 
+//@<OUT> tokenizeStatement
+[
+    {
+        "token": "select",
+        "type": "SELECT_SYMBOL"
+    },
+    {
+        "token": " ",
+        "type": "WHITESPACE"
+    },
+    {
+        "token": "*",
+        "type": "MULT_OPERATOR"
+    },
+    {
+        "token": " ",
+        "type": "WHITESPACE"
+    },
+    {
+        "token": "from",
+        "type": "FROM_SYMBOL"
+    },
+    {
+        "token": " ",
+        "type": "WHITESPACE"
+    },
+    {
+        "token": "/* comment */",
+        "type": "BLOCK_COMMENT"
+    },
+    {
+        "token": " ",
+        "type": "WHITESPACE"
+    },
+    {
+        "token": "foo",
+        "type": "IDENTIFIER"
+    },
+    {
+        "token": ".",
+        "type": "DOT_SYMBOL"
+    },
+    {
+        "token": "bar",
+        "type": "IDENTIFIER"
+    },
+    {
+        "token": " ",
+        "type": "WHITESPACE"
+    },
+    {
+        "token": "/* `\"'` '*/",
+        "type": "BLOCK_COMMENT"
+    }
+]
+
+//@<ERR> tokenizeStatement errors
+token recognition error at: ''' (RuntimeError)
+token recognition error at: '`' (RuntimeError)
+token recognition error at: '"' (RuntimeError)
+token recognition error at: '\' (RuntimeError)
+
 //@<OUT> splitScript
 [
     "select 1"
diff --git a/unittest/scripts/js_devapi/validation/mysqlx_session_sql.js b/unittest/scripts/js_devapi/validation/mysqlx_session_sql.js
index aee9c5e6c5..eacae8bb1c 100644
--- a/unittest/scripts/js_devapi/validation/mysqlx_session_sql.js
+++ b/unittest/scripts/js_devapi/validation/mysqlx_session_sql.js
@@ -7,13 +7,16 @@
 1 row in set [[*]]
 
 //@ WL12813 SQL Test With Error (missing placeholder)
-||You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '?' at line 1 (MySQL Error 1064)
+||Insufficient number of values for placeholders in query (ArgumentError)
 
 //@<OUT> runSql with various parameter types
 [
     null,
     1234,
     "-0.12345",
-    "3.14159",
+    "3.14159265359",
     "hellooooo"
-]
\ No newline at end of file
+]
+
+//@<OUT> BUG#34715428: runSql with ! placeholders
+<<<__user>>>
diff --git a/unittest/scripts/js_devapi/validation/mysqlx_session_sql_prepared.js b/unittest/scripts/js_devapi/validation/mysqlx_session_sql_prepared.js
index 5fb66bfe81..5b83ba1e93 100644
--- a/unittest/scripts/js_devapi/validation/mysqlx_session_sql_prepared.js
+++ b/unittest/scripts/js_devapi/validation/mysqlx_session_sql_prepared.js
@@ -1,15 +1,6 @@
 //@<PROTOCOL> First execution is normal
 >>>> SEND Mysqlx.Sql.StmtExecute {
-  stmt: "select * from test_table where name like ?"
-  args {
-    type: SCALAR
-    scalar {
-      type: V_STRING
-      v_string {
-        value: "g%"
-      }
-    }
-  }
+  stmt: "select * from test_table where name like \'g%\'"
   namespace: "sql"
 }
 
diff --git a/unittest/scripts/manual/upgrade_metadata_crash_handling.js b/unittest/scripts/manual/upgrade_metadata_crash_handling.js
index 93b1960e81..63d469bdec 100644
--- a/unittest/scripts/manual/upgrade_metadata_crash_handling.js
+++ b/unittest/scripts/manual/upgrade_metadata_crash_handling.js
@@ -24,7 +24,7 @@ function upgrade_and_fail(num_queries, test_crash) {
 
     println("\033[1;31mUPGRADING", "\033[0m");
     // perform upgrade and ensure it crashes after X queries
-    r=testutil.callMysqlsh([url, "--js", "--verbose=3", "--dba-log-sql=2",
+    r=testutil.callMysqlsh([url, "--js", "--verbose=3", "--log-sql=unfiltered",
                     "--debug=+d,"+failure, 
                     "--", "dba", "upgrade-metadata"], 
                     "", ["TEST_SQL_UNTIL_CRASH="+num_queries], mysqlshrec);
@@ -35,7 +35,7 @@ function upgrade_and_fail(num_queries, test_crash) {
 function recover() {
     println("\033[1;31mRECOVERING", "\033[0m");
 
-    r = testutil.callMysqlsh([url, "--js", "--verbose=2", "--dba-log-sql=2",
+    r = testutil.callMysqlsh([url, "--js", "--verbose=2", "--log-sql=unfiltered",
         "--", "dba", "upgrade-metadata"]);
 
     println("\033[1;31mRECOVERY RC=",r, "\033[0m");
@@ -53,7 +53,7 @@ function recover_and_fail(num_queries, test_crash) {
     else
         failure = "sql_test_error";
 
-    r = testutil.callMysqlsh([url, "--js", "--verbose=2", "--dba-log-sql=2",
+    r = testutil.callMysqlsh([url, "--js", "--verbose=2", "--log-sql=unfiltered",
         "--debug=+d,"+failure, 
         "--", "dba", "upgrade-metadata"],
         "", ["TEST_SQL_UNTIL_CRASH="+num_queries], mysqlshrec);
diff --git a/unittest/scripts/py_devapi/scripts/mysql_module.py b/unittest/scripts/py_devapi/scripts/mysql_module.py
index a673f40f01..89bd00fa17 100644
--- a/unittest/scripts/py_devapi/scripts/mysql_module.py
+++ b/unittest/scripts/py_devapi/scripts/mysql_module.py
@@ -18,6 +18,7 @@
   'Type',
   'help',
   'parse_statement_ast',
+  'tokenize_statement',
   'quote_identifier',
   'split_script',
   'unquote_identifier'])
diff --git a/unittest/scripts/py_devapi/scripts/mysql_session.py b/unittest/scripts/py_devapi/scripts/mysql_session.py
index 04a75eead4..9cb537af82 100644
--- a/unittest/scripts/py_devapi/scripts/mysql_session.py
+++ b/unittest/scripts/py_devapi/scripts/mysql_session.py
@@ -9,15 +9,19 @@
 validate_members(classicSession, [
   'close',
   'commit',
+  'get_client_data',
   'get_connection_id',
   'get_uri',
+  'get_sql_mode',
   'get_ssh_uri',
   'help',
   'is_open',
   'rollback',
   'run_sql',
   'start_transaction',
+  'set_client_data',
   'set_query_attributes',
+  'track_system_variable',
   'connection_id',
   'uri',
   'ssh_uri',
@@ -91,6 +95,9 @@
 #@<OUT> ClassicSession: run_sql placeholders
 classicSession.run_sql("select ?, ?", ['hello', 1234])
 
+#@<> ClassicSession: runSql with ! placeholders
+assert __user == classicSession.run_sql('select user from !.! where user=?', ['mysql', 'user', __user]).fetch_one()[0]
+
 # Cleanup
 classicSession.run_sql('drop schema if exists classic_session_schema')
 classicSession.close()
diff --git a/unittest/scripts/py_devapi/scripts/mysqlx_session.py b/unittest/scripts/py_devapi/scripts/mysqlx_session.py
index 83182eb562..ed9aacd543 100644
--- a/unittest/scripts/py_devapi/scripts/mysqlx_session.py
+++ b/unittest/scripts/py_devapi/scripts/mysqlx_session.py
@@ -11,11 +11,13 @@
   'commit',
   'create_schema',
   'drop_schema',
+  'get_client_data',
   'get_connection_id',
   'get_current_schema',
   'get_default_schema',
   'get_schema',
   'get_schemas',
+  'get_sql_mode',
   'get_ssh_uri',
   'get_uri',
   'help',
@@ -32,6 +34,7 @@
   'uri',
   'current_schema',
   'set_savepoint',
+  'set_client_data',
   'ssh_uri',
   'release_savepoint',
   'rollback_to',
diff --git a/unittest/scripts/py_devapi/scripts/mysqlx_session_sql.py b/unittest/scripts/py_devapi/scripts/mysqlx_session_sql.py
index 5b993a93fa..c41bd25adb 100644
--- a/unittest/scripts/py_devapi/scripts/mysqlx_session_sql.py
+++ b/unittest/scripts/py_devapi/scripts/mysqlx_session_sql.py
@@ -30,6 +30,9 @@
 #@ WL12813 SQL Test 08 [USE:WL12813 SQL Test 01]
 session.sql('SELECT * FROM wl12813 WHERE doc->>\'$."notnested.like"\' = ?').bind('foo').execute()
 
+#@<> BUG#34715428: runSql with ! placeholders
+assert __user == session.run_sql('select user from !.! where user=?', ['mysql', 'user', __user]).fetch_one()[0]
+
 #@<> Finalizing
 session.drop_schema('session_sql')
 session.close()
diff --git a/unittest/scripts/py_devapi/validation/mysqlx_session_sql_prepared.py b/unittest/scripts/py_devapi/validation/mysqlx_session_sql_prepared.py
index 3847bd8b55..ab667f31a4 100644
--- a/unittest/scripts/py_devapi/validation/mysqlx_session_sql_prepared.py
+++ b/unittest/scripts/py_devapi/validation/mysqlx_session_sql_prepared.py
@@ -1,15 +1,6 @@
 #@<PROTOCOL> First execution is normal
 >>>> SEND Mysqlx.Sql.StmtExecute {
-  stmt: "select * from test_table where name like ?"
-  args {
-    type: SCALAR
-    scalar {
-      type: V_STRING
-      v_string {
-        value: "g%"
-      }
-    }
-  }
+  stmt: "select * from test_table where name like \'g%\'"
   namespace: "sql"
 }
 
diff --git a/unittest/scripts/setup_js/setup.js b/unittest/scripts/setup_js/setup.js
index adf99b05ca..40e218f976 100644
--- a/unittest/scripts/setup_js/setup.js
+++ b/unittest/scripts/setup_js/setup.js
@@ -249,6 +249,8 @@ function hasAuthEnvironment(context) {
                  'LDAP_KERBEROS_SERVER_PORT',
                  'LDAP_KERBEROS_BIND_BASE_DN',
                  'LDAP_KERBEROS_USER_SEARCH_ATTR',
+                 'LDAP_KERBEROS_BIND_ROOT_DN',
+                 'LDAP_KERBEROS_BIND_ROOT_PWD',
                  'LDAP_KERBEROS_USER',
                  'LDAP_KERBEROS_PWD',
                  'LDAP_KERBEROS_AUTH_STRING',
@@ -332,7 +334,7 @@ function getAuthServerConfig(context) {
 
     return {
       "plugin-load-add": `authentication_kerberos.${ext}`,
-      "loose-authentication_kerberos_service_principal": `mysql_service/kerberos_auth_host@${LDAP_KERBEROS_DOMAIN}`,
+      "loose-authentication_kerberos_service_principal": `mysql_service/kerberos_auth_host@${KERBEROS_DOMAIN}`,
       "loose-authentication_kerberos_service_key_tab": keytab_file,
       "net_read_timeout": 360,
       "connect_timeout": 360,
@@ -389,9 +391,9 @@ function isAuthMethodSupported(context, uri=__mysqluripwd) {
   try {
     ensure_plugin_enabled(plugin, s);
   } catch (error) {
-    plugin_supported = error.code == PLUGIN_ALREADY_EXISTS;
+    plugin_supported = get_exception_code(error) == PLUGIN_ALREADY_EXISTS;
     if (!plugin_supported) {
-      shell.log('warning', `Unable to get ${plugin} installed: Error ${error.code}: ${error.message}`)
+      shell.log('warning', `Unable to get ${plugin} installed: Error ${get_exception_code(error)}: ${get_exception_message(error)}`)
     }
   }
 
@@ -411,6 +413,25 @@ function isAuthMethodSupported(context, uri=__mysqluripwd) {
 }
 
 
+function destroy_kerberos_cache() {
+  var command;
+
+  if ("windows" == __os_type) {
+    command = "klist purge";
+  } else {
+    command = os.path.join(__bin_dir, "kdestroy");
+
+    if (!os.path.isfile(command)) {
+      command = "kdestroy";
+    }
+  }
+
+  WIPE_OUTPUT();
+  testutil.callMysqlsh(["--py", "-i", "-e", `import os;print('result:',os.system('${command}'))`]);
+  EXPECT_STDOUT_CONTAINS("result: 0");
+}
+
+
 function set_sysvar(session, variable, value) {
   session.runSql("SET GLOBAL " + variable + " = ?", [value]);
 }
@@ -860,43 +881,9 @@ function wait(timeout, wait_interval, condition) {
   return res;
 }
 
-// --------
-
-function begin_dba_log_sql(level) {
-  shell.options["dba.logSql"] = level ? level : 1;
-  var dummy = testutil.fetchDbaSqlLog(true);
-}
-
-function end_dba_log_sql() {
-  var logs = testutil.fetchDbaSqlLog(false);
-  shell.options["dba.logSql"] = 0;
-  return logs;
-}
 
 // -------- Test Expectations
 
-
-function EXPECT_NO_SQL(instance, logs, allowed_stmts) {
-  var fail = false;
-  for(var i in logs) {
-    var line = logs[i];
-    if (line.startsWith(instance)) {
-      var bad = false;
-      for (var j in allowed_stmts) {
-        if (!line.split(": ")[1].startsWith(allowed_stmts[j])) {
-          bad = true;
-          break;
-        }
-      }
-      if (bad) {
-        println("UNEXPECTED SQL DETECTED:", line);
-        fail = true;
-      }
-    }
-  }
-  EXPECT_FALSE(fail);
-}
-
 function EXPECT_SQL(instance, logs, expected_stmt) {
   var fail = true;
   for(var i in logs) {
@@ -1104,9 +1091,17 @@ function get_exception_message(exception) {
       return exception.cause.message;
     }
   }
-  return exception.message
+  return exception.message;
 }
 
+function get_exception_code(exception){
+  if (exception.cause != undefined) {
+    if (exception.cause.code != undefined) {
+      return exception.cause.code;
+    }
+  }
+  return exception.code;
+}
 
 function EXPECT_THROWS(func, etext) {
   if (typeof(etext) != "string" && typeof(etext) != "object") {
diff --git a/unittest/scripts/setup_py/copy_utils.inc b/unittest/scripts/setup_py/copy_utils.inc
index ebec79e60f..3127b8fde0 100644
--- a/unittest/scripts/setup_py/copy_utils.inc
+++ b/unittest/scripts/setup_py/copy_utils.inc
@@ -14,8 +14,9 @@ def setup_copy_tests(arg_no, src_options = {}):
     setup_session(__sandbox_uri1)
     analyze_tables(session)
     session.run_sql("CREATE EVENT sakila.existing_event ON SCHEDULE EVERY 1 YEAR DO BEGIN END")
-    testutil.deploy_sandbox(__mysql_sandbox_port2, "root", { "local_infile": 1 })
+    testutil.deploy_sandbox(__mysql_sandbox_port2, "root", { "local_infile": 1, "innodb_doublewrite": "OFF" })
     globals()["tgt_session"] = mysql.get_session(__sandbox_uri2)
+    tgt_session.run_sql("/*!80021 alter instance disable innodb redo_log */")
     if instance_supports_libraries:
         # make sure that the MLE component is disabled, tests will enable it when needed
         # WL16731-TSFR_1_1_2 - tests are executed with MLE component disabled
diff --git a/unittest/scripts/setup_py/diags_common.inc b/unittest/scripts/setup_py/diags_common.inc
index eea4f46ee0..be34812a10 100644
--- a/unittest/scripts/setup_py/diags_common.inc
+++ b/unittest/scripts/setup_py/diags_common.inc
@@ -219,7 +219,7 @@ def CHECK_METADATA(session, path):
 def CHECK_INSTANCE(session, zf, instance_id, expected_files_per_instance, innodbMutex, localTarget):
     for q, fn, options in expected_files_per_instance:
         minver = options.get("version", None)
-        if minver and ((minver>0 and __version_num < minver) or (minver<0 and __version_num > -minver)):
+        if minver and ((minver>0 and __version_num < minver) or (minver<0 and __version_num >= -minver)):
             continue
         if options.get("innodbMutex") and not innodbMutex:
             continue
@@ -242,7 +242,7 @@ def CHECK_INSTANCE(session, zf, instance_id, expected_files_per_instance, innodb
                 CHECK_QUERY(session, data, q, f"{instance_id}: {fn}")
 
 
-def CHECK_DIAGPACK(file, sessions_or_errors, is_cluster=False, innodbMutex=False, allMembers=1, schemaStats=False, slowQueries=False, localTarget=False):
+def CHECK_DIAGPACK(file, sessions_or_errors, is_cluster=False, innodbMutex=False, allMembers=1, schemaStats=False, slowQueries=False, localTarget=False, hostInfo=True):
     if __os_type != "windows":
         EXPECT_EQ(0o600, os.stat(file).st_mode & 0o777, file) # TSFR_1_3_1
     diag_type = "basic"
@@ -270,7 +270,7 @@ def CHECK_DIAGPACK(file, sessions_or_errors, is_cluster=False, innodbMutex=False
         ZEXPECT_NOT_EMPTY(zf, "shell_info.yaml")
         if diag_type == "basic":
             ZEXPECT_NOT_EMPTY(zf, "mysqlsh.log")
-        if localTarget:
+        if localTarget and hostInfo:
             ZEXPECT_NOT_EMPTY(zf, "host_info")
         filelist = zf.namelist()
         if is_cluster:
diff --git a/unittest/scripts/setup_py/dump_binlogs_utils.inc b/unittest/scripts/setup_py/dump_binlogs_utils.inc
index 7b48c0def6..c194c4425a 100644
--- a/unittest/scripts/setup_py/dump_binlogs_utils.inc
+++ b/unittest/scripts/setup_py/dump_binlogs_utils.inc
@@ -40,6 +40,7 @@ mysqld_options = {
     "enforce_gtid_consistency": "ON",
     "gtid_mode": "ON",
     "max_binlog_size": str(128 * 1024),  # small binlog size, so that it rotates often
+    "innodb_doublewrite": "OFF",
 }
 
 # helpers
diff --git a/unittest/scripts/setup_py/dump_utils.inc b/unittest/scripts/setup_py/dump_utils.inc
index b47a620593..b597cb07e4 100644
--- a/unittest/scripts/setup_py/dump_utils.inc
+++ b/unittest/scripts/setup_py/dump_utils.inc
@@ -404,6 +404,11 @@ def create_invisible_pks_auto_increment_conflict(schema, table):
     fixed = ""
     return Compatibility_issue(error, fixed)
 
+def create_invisible_pks_partitioned_table(schema, table):
+    error = f"Table `{schema}`.`{table}` does not have a Primary Key, this cannot be fixed automatically because the table is partitioned (this issue needs to be fixed manually)"
+    fixed = ""
+    return Compatibility_issue(error, fixed)
+
 def ignore_missing_pks(schema, table):
     error = f"Table `{schema}`.`{table}` does not have a Primary Key, which is required for High Availability in MySQL HeatWave Service"
     fixed = f"Table `{schema}`.`{table}` does not have a Primary Key, this is ignored"
diff --git a/unittest/shell_cli_operation_t.cc b/unittest/shell_cli_operation_t.cc
index 786aba79a8..09f829be03 100644
--- a/unittest/shell_cli_operation_t.cc
+++ b/unittest/shell_cli_operation_t.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -804,9 +804,9 @@ TEST_F(Shell_cli_operation_test, error_test) {
   std::vector<std::string> env{"MYSQLSH_TERM_COLOR_MODE=nocolor"};
 
   testutil->call_mysqlsh_c(
-      {"--", "cluster", "rescan", "{--addInstances : 'auto'}"}, "", env);
+      {"--", "cluster", "rescan", "{--addUnmanaged : true}"}, "", env);
   MY_EXPECT_STDOUT_CONTAINS(
-      "Error at '--addInstances : 'auto'}'.\n"
+      "Error at '--addUnmanaged : true}'.\n"
       "Invalid format for command line argument. Valid formats are:\n");
   output_handler.wipe_all();
 }
diff --git a/unittest/shell_cmdline_options_t.cc b/unittest/shell_cmdline_options_t.cc
index a8126666bf..861a62dc9b 100644
--- a/unittest/shell_cmdline_options_t.cc
+++ b/unittest/shell_cmdline_options_t.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2014, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -992,20 +992,11 @@ TEST_F(Shell_cmdline_options, test_session_type_conflicts) {
   test_session_type_conflicts("--mysql", "--sqlx", 1);
   test_session_type_conflicts("--mysql", "--mysqlx", 0);
 
-  test_session_type_conflicts("-ma", "--mysql", 0);
-  test_session_type_conflicts("--mysql", "-ma", 0);
-  test_session_type_conflicts("-ma", "--mysqlx", 0);
-  test_session_type_conflicts("--mysqlx", "-ma", 0);
-  test_session_type_conflicts("--mc", "-ma", 0);
-  test_session_type_conflicts("--mx", "-ma", 0);
-
   test_session_type_conflicts("mysql://root@localhost", "--sqlc", 0);
   test_session_type_conflicts("mysql://root@localhost", "--mysql", 0);
-  test_session_type_conflicts("mysql://root@localhost", "-ma", 0);
 
   test_session_type_conflicts("mysqlx://root@localhost", "--sqlx", 0);
   test_session_type_conflicts("mysqlx://root@localhost", "--mysqlx", 0);
-  test_session_type_conflicts("mysqlx://root@localhost", "-ma", 0);
 
   test_session_type_conflicts("mysql://root@localhost", "--sqlx", 1);
   test_session_type_conflicts("mysqlx://root@localhost", "--sqlc", 1);
diff --git a/unittest/shell_cmdline_regressions_t.cc b/unittest/shell_cmdline_regressions_t.cc
index f473437df5..8315a886b9 100644
--- a/unittest/shell_cmdline_regressions_t.cc
+++ b/unittest/shell_cmdline_regressions_t.cc
@@ -1,4 +1,4 @@
-/* Copyright (c) 2017, 2024, Oracle and/or its affiliates.
+/* Copyright (c) 2017, 2025, Oracle and/or its affiliates.
 
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License, version 2.0,
@@ -69,68 +69,6 @@ TEST_F(Command_line_test, bug24912358) {
   }
 }
 
-#ifdef HAVE_JS
-TEST_F(Command_line_test, bug23508428) {
-  // Test if the xplugin is installed using enableXProtocol in the --dba option
-  // In 8.0.4, the mysqlx_cache_cleaner is also supposed to be installed
-  // In 8.0.11+, both plugins are built-in, cannot be uninstalled
-  std::string uri = "--uri=" + _mysql_uri;
-
-  execute({_mysqlsh, uri.c_str(), "--sqlc", "-e", "uninstall plugin mysqlx;",
-           NULL});
-  if (_target_server_version >= mysqlshdk::utils::Version(8, 0, 5)) {
-    MY_EXPECT_CMD_OUTPUT_CONTAINS(
-        "ERROR: 1619 (HY000) at line 1: Built-in plugins "
-        "cannot be deleted");
-  }
-
-  if (_target_server_version >= mysqlshdk::utils::Version(8, 0, 4)) {
-    execute({_mysqlsh, uri.c_str(), "--sqlc", "-e",
-             "uninstall plugin mysqlx_cache_cleaner;", NULL});
-    if (_target_server_version >= mysqlshdk::utils::Version(8, 0, 5)) {
-      MY_EXPECT_CMD_OUTPUT_CONTAINS(
-          "ERROR: 1619 (HY000) at line 1: Built-in plugins "
-          "cannot be deleted");
-    }
-  }
-
-  if (_target_server_version < mysqlshdk::utils::Version(8, 0, 5)) {
-    execute(
-        {_mysqlsh, uri.c_str(), "--mysql", "--dba", "enableXProtocol", NULL});
-
-    MY_EXPECT_CMD_OUTPUT_CONTAINS(
-        "WARNING: The enableXProtocol option was deprecated.");
-    MY_EXPECT_CMD_OUTPUT_CONTAINS(
-        "enableXProtocol: Installing plugin "
-        "mysqlx...");
-    MY_EXPECT_CMD_OUTPUT_CONTAINS(
-        "enableXProtocol: successfully installed the X protocol plugin!");
-  }
-
-  execute({_mysqlsh, uri.c_str(), "--interactive=full", "--js", "-e",
-           "session.runSql('SELECT COUNT(*) FROM information_schema.plugins "
-           "WHERE PLUGIN_NAME in (\"mysqlx\", \"mysqlx_cache_cleaner\")')."
-           "fetchOne()",
-           NULL});
-  MY_EXPECT_CMD_OUTPUT_CONTAINS("[");
-  if (_target_server_version >= mysqlshdk::utils::Version(8, 0, 4)) {
-    MY_EXPECT_CMD_OUTPUT_CONTAINS("    2");
-  } else {
-    MY_EXPECT_CMD_OUTPUT_CONTAINS("    1");
-  }
-  MY_EXPECT_CMD_OUTPUT_CONTAINS("]");
-
-  execute({_mysqlsh, uri.c_str(), "--mysql", "--js", "--dba", "enableXProtocol",
-           NULL});
-  MY_EXPECT_CMD_OUTPUT_CONTAINS(
-      "WARNING: The enableXProtocol option was deprecated.");
-  MY_EXPECT_CMD_OUTPUT_CONTAINS(
-      "enableXProtocol: The X Protocol plugin is already enabled and listening "
-      "for connections on port " +
-      _port);
-}
-#endif
-
 TEST_F(Command_line_test, bug24905066) {
   // Tests URI formatting using classic protocol
   {
diff --git a/unittest/shell_log_sql_t.cc b/unittest/shell_log_sql_t.cc
index 22adde5cac..569c42cf74 100644
--- a/unittest/shell_log_sql_t.cc
+++ b/unittest/shell_log_sql_t.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -67,15 +67,13 @@ char sql_filter_error[] = "showasdf;";
 
 struct Log_sql_test_params {
   std::string context_name;
-  int dba_log_level;          // (0),1,2
   std::string log_sql_level;  // off,(error),on,all,unfiltered
   std::array<int, 11> log_count_mask;
 };
 
 std::ostream &operator<<(std::ostream &os, const Log_sql_test_params &p) {
   os << "Log_sql_test_params(context_name=" << p.context_name
-     << ", dba_log=" << p.dba_log_level << ", log_sql=" << p.log_sql_level
-     << ")";
+     << ", log_sql=" << p.log_sql_level << ")";
   return os;
 }
 
@@ -176,10 +174,6 @@ TEST_P(Shell_log_sql_parameterized_tests, log_sql) {
 
   Log_sql_guard g(params.context_name.c_str());
 
-  // (0),1,2
-  mysqlsh::current_shell_options()->set_and_notify(
-      "dba.logSql", std::to_string(params.dba_log_level));
-
   // off,(error),on,all,unfiltered
   mysqlsh::current_shell_options()->set_and_notify("logSql",
                                                    params.log_sql_level);
@@ -222,108 +216,32 @@ INSTANTIATE_TEST_SUITE_P(
     Shell_log_sql, Shell_log_sql_parameterized_tests,
     ::testing::Combine(
         ::testing::Values(
+            Log_sql_test_params{"js", "off", {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
             Log_sql_test_params{
-                "js", 0, "off", {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
-            Log_sql_test_params{
-                "js", 0, "error", {0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0}},
-            Log_sql_test_params{
-                "js", 0, "on", {1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1}},
+                "js", "error", {0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0}},
+            Log_sql_test_params{"js", "on", {1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1}},
             Log_sql_test_params{
-                "js", 0, "unfiltered", {1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0}},
+                "js", "unfiltered", {1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0}},
 
             Log_sql_test_params{
-                "js", 1, "off", {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
+                "Dba.test", "off", {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
             Log_sql_test_params{
-                "js", 1, "error", {0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0}},
+                "Dba.test", "error", {0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0}},
             Log_sql_test_params{
-                "js", 1, "on", {1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1}},
+                "Dba.test", "on", {1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1}},
             Log_sql_test_params{
-                "js", 1, "unfiltered", {1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0}},
-
-            Log_sql_test_params{
-                "js", 2, "off", {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
-            Log_sql_test_params{
-                "js", 2, "error", {0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0}},
-            Log_sql_test_params{
-                "js", 2, "on", {1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1}},
-            Log_sql_test_params{
-                "js", 2, "unfiltered", {1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0}},
-
-            Log_sql_test_params{
-                "Dba.test", 0, "off", {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
-            Log_sql_test_params{
-                "Dba.test", 0, "error", {0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0}},
-            Log_sql_test_params{
-                "Dba.test", 0, "on", {1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1}},
-            Log_sql_test_params{
-                "Dba.test", 0, "unfiltered", {1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0}},
-
-            Log_sql_test_params{
-                "Dba.test", 1, "off", {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0}},
-            Log_sql_test_params{
-                "Dba.test", 1, "error", {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0}},
-            Log_sql_test_params{
-                "Dba.test", 1, "on", {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0}},
-            Log_sql_test_params{
-                "Dba.test", 1, "unfiltered", {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0}},
-
-            Log_sql_test_params{
-                "Dba.test", 2, "off", {1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0}},
-            Log_sql_test_params{
-                "Dba.test", 2, "error", {1, 0, 2, 1, 1, 1, 1, 1, 1, 1, 0}},
-            Log_sql_test_params{
-                "Dba.test", 2, "on", {1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0}},
-            Log_sql_test_params{
-                "Dba.test", 2, "unfiltered", {1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0}},
+                "Dba.test", "unfiltered", {1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0}},
 
             Log_sql_test_params{"Cluster.test",
-                                0,
                                 "off",
                                 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
             Log_sql_test_params{"Cluster.test",
-                                0,
                                 "error",
                                 {0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0}},
             Log_sql_test_params{"Cluster.test",
-                                0,
                                 "on",
                                 {1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1}},
             Log_sql_test_params{"Cluster.test",
-                                0,
-                                "unfiltered",
-                                {1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0}},
-
-            Log_sql_test_params{"Cluster.test",
-                                1,
-                                "off",
-                                {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0}},
-            Log_sql_test_params{"Cluster.test",
-                                1,
-                                "error",
-                                {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0}},
-            Log_sql_test_params{"Cluster.test",
-                                1,
-                                "on",
-                                {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0}},
-            Log_sql_test_params{"Cluster.test",
-                                1,
-                                "unfiltered",
-                                {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0}},
-
-            Log_sql_test_params{"Cluster.test",
-                                2,
-                                "off",
-                                {1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0}},
-            Log_sql_test_params{"Cluster.test",
-                                2,
-                                "error",
-                                {1, 0, 2, 1, 1, 1, 1, 1, 1, 1, 0}},
-            Log_sql_test_params{"Cluster.test",
-                                2,
-                                "on",
-                                {1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0}},
-            Log_sql_test_params{"Cluster.test",
-                                2,
                                 "unfiltered",
                                 {1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0}}),
         ::testing::Values("Classic", "X")));
diff --git a/unittest/shell_script_tester.cc b/unittest/shell_script_tester.cc
index 071c004999..0f4d604197 100644
--- a/unittest/shell_script_tester.cc
+++ b/unittest/shell_script_tester.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2015, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
diff --git a/unittest/test_utils.cc b/unittest/test_utils.cc
index 1f4b7729da..d32f6a34c1 100644
--- a/unittest/test_utils.cc
+++ b/unittest/test_utils.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2015, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -170,12 +170,6 @@ void Shell_test_output_handler::log_hook(const shcore::Logger::Log_entry &entry,
   // If the level of the log is different than
   // the one set, we don't want to store the message
   if (current_level == entry.level) self->log.emplace_back(entry.message);
-
-  if (entry.domain == "dba.sql") {
-    if (!self->dba_sql_log) self->dba_sql_log = shcore::make_array();
-    self->dba_sql_log->push_back(shcore::Value(
-        std::to_string(entry.timestamp) + " " + std::string(entry.message)));
-  }
 }
 
 bool Shell_test_output_handler::deleg_print(void *user_data, const char *text) {
diff --git a/unittest/test_utils.h b/unittest/test_utils.h
index b7b9b88e16..ebf1856747 100644
--- a/unittest/test_utils.h
+++ b/unittest/test_utils.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2015, 2024, Oracle and/or its affiliates.
+/* Copyright (c) 2015, 2025, Oracle and/or its affiliates.
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License, version 2.0,
@@ -173,7 +173,6 @@ class Shell_test_output_handler {
   std::string internal_std_out;
   std::stringstream full_output;
   std::mutex stdout_mutex;
-  shcore::Array_t dba_sql_log = nullptr;
   static std::vector<std::string> log;
 
   void set_log_level(shcore::Logger::LOG_LEVEL log_level) {
diff --git a/unittest/test_utils/mocks/mysqlshdk/libs/db/mock_session.h b/unittest/test_utils/mocks/mysqlshdk/libs/db/mock_session.h
index cf83234e85..9907462d86 100644
--- a/unittest/test_utils/mocks/mysqlshdk/libs/db/mock_session.h
+++ b/unittest/test_utils/mocks/mysqlshdk/libs/db/mock_session.h
@@ -133,6 +133,7 @@ class Mock_session : public mysqlshdk::db::ISession,
   MOCK_CONST_METHOD0(get_server_version, mysqlshdk::utils::Version());
 
   MOCK_CONST_METHOD0(get_socket_fd, socket_t());
+  MOCK_METHOD1(track_system_variable, std::string(const std::string &));
 
   // Error handling
   MOCK_CONST_METHOD0(get_last_error, mysqlshdk::db::Error *());
diff --git a/unittest/test_utils/mod_testutils.cc b/unittest/test_utils/mod_testutils.cc
index 9325887331..18218b97f1 100644
--- a/unittest/test_utils/mod_testutils.cc
+++ b/unittest/test_utils/mod_testutils.cc
@@ -298,9 +298,6 @@ Testutils::Testutils(const std::string &sandbox_dir, bool dummy_mode,
   expose("assertNoPrompts", &Testutils::assert_no_prompts);
   expose("fetchCapturedStdout", &Testutils::fetch_captured_stdout, "?eatOne");
   expose("fetchCapturedStderr", &Testutils::fetch_captured_stderr, "?eatOne");
-#ifndef ENABLE_SESSION_RECORDING
-  expose("fetchDbaSqlLog", &Testutils::fetch_dba_sql_log, "?flush");
-#endif
 
   expose("callMysqlsh", &Testutils::call_mysqlsh, "args", "?stdInput", "?envp",
          "?execPath");
@@ -1169,28 +1166,6 @@ std::string Testutils::get_shell_log_path() {
   return shcore::current_logger()->logfile_name();
 }
 
-#ifndef ENABLE_SESSION_RECORDING
-//!<  @name Misc Utilities
-///@{
-/**
- * Gets (and optionally flushes) the contents of the dba SQL log.
- */
-#if DOXYGEN_JS
-Array Testutils::fetchDbaSqlLog(Boolean);
-#elif DOXYGEN_PY
-list Testutils::fetch_dba_sql_log(bool);
-#endif
-///@}
-shcore::Array_t Testutils::fetch_dba_sql_log(bool flush) {
-  shcore::Array_t log = static_cast<Shell_core_test_wrapper *>(_test_env)
-                            ->output_handler.dba_sql_log;
-  if (flush)
-    static_cast<Shell_core_test_wrapper *>(_test_env)
-        ->output_handler.dba_sql_log.reset();
-  return log;
-}
-#endif
-
 //!<  @name Testing Utilities
 ///@{
 /**
diff --git a/unittest/test_utils/mod_testutils.h b/unittest/test_utils/mod_testutils.h
index f7f522919c..9bd63dd395 100644
--- a/unittest/test_utils/mod_testutils.h
+++ b/unittest/test_utils/mod_testutils.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2017, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -74,9 +74,6 @@ class Testutils : public mysqlsh::Extensible_object {
   String getSandboxPath(Integer port, String name);
   List readGeneralLog(Integer port, String startingTimestamp = "");
   String getShellLogPath();
-#ifndef ENABLE_SESSION_RECORDING
-  Array fetchDbaSqlLog(Boolean flush);
-#endif
   Undefined dumpData(String uri, String path, Array schemaList);
   Undefined importData(String uri, String path, String defaultSchema,
                        String importCharset);
@@ -144,7 +141,7 @@ class Testutils : public mysqlsh::Extensible_object {
   list read_general_log(int port, str startingTimestamp = "");
   str get_shell_log_path();
 #ifndef ENABLE_SESSION_RECORDING
-  list fetch_dba_sql_log(bool flush);
+  list fetch_sql_log(bool flush);
 #endif
   None dump_data(str uri, str path, list schemaList);
   None import_data(str uri, str path, str defaultSchema, str defaultCharset);
@@ -324,7 +321,7 @@ class Testutils : public mysqlsh::Extensible_object {
 
   std::string get_shell_log_path();
 #ifndef ENABLE_SESSION_RECORDING
-  shcore::Array_t fetch_dba_sql_log(bool flush);
+  shcore::Array_t fetch_sql_log(bool flush);
 #endif
 
   shcore::Array_t grep_file(const std::string &path,
diff --git a/unittest/utils_mysql_parsing_t.cc b/unittest/utils_mysql_parsing_t.cc
index cba2be961d..6c097b95e8 100644
--- a/unittest/utils_mysql_parsing_t.cc
+++ b/unittest/utils_mysql_parsing_t.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2024, Oracle and/or its affiliates.
+ * Copyright (c) 2014, 2025, Oracle and/or its affiliates.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2.0,
@@ -1379,6 +1379,10 @@ select $;)*"));
   // select 1, \w\d @ 2, 3@
 }
 
+#if __GNUC__ >= 15 && !defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wfree-nonheap-object"
+#endif
 TEST_P(Statement_splitter, line_numbering) {
   // clang-format off
   {
@@ -1465,6 +1469,9 @@ TEST_P(Statement_splitter, line_numbering) {
   }
   // clang-format on
 }
+#if __GNUC__ >= 15 && !defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
 
 void dump(const std::vector<std::string> &v) {
   for (const auto &s : v) std::cout << "[[" << s << "]\n";
