Is it safe to access the underlying mysqli object from \wpdb for custom queries?

I am writing a custom (bespoke, for 1 client, not for publication and general consumption) plugin, that needs to access custom tables.

In general with WP,I follow the principle of using the higher level APIs where possible, so rarely have to work with \wpdb. Now that i do have a need, i am discovering how archaic it is!

Looking through the source code, it seems I can be sure that it is using mysqli (since the plugin requires php 7.1, and we also control the server) so I thought why not use that for my custom tables logic?

I wrote a tiny wrapper:

<?php  namespace PluginNamespaceHere\DB;  use mysqli; use wpdb;  /**  * A small wrapper class that contains, and provides direct access to, the $  wpdb object,  * as well as the underlying MYSQLI object, so we can do real prepared statements etc  */ class DB {      /** @var wpdb */     public $  wpdb;      /** @var mysqli */     public $  mysqli;      /**      * @param wpdb $  wpdb      */     public function __construct(wpdb $  wpdb)     {         $  this->wpdb = $  wpdb;         //$  wpdb is protected, but accessible via magic __get() wp-db.php line: 643         $  this->mysqli = $  wpdb->dbh;     }       /**      * Run an SQL query. If $  params are provided, prepared statements are used. If $  bind_types are provided, they will be      * used in the prepared statement, if not, all params will be treated as strings      *      * @param string $  sql The SQL string, unprefixed table names should be wrapped in curly braces eg SELECT * FROM {posts}      * @param array $  params Optional parameters for prepared statements      * @param string $  bind_types Optional bind types for prepared statements, defaults to string      * @retun bool|mysqli_result      */     public function run($  sql, $  params=[], $  bind_types='')     {         $  sql = $  this->prefixTableNamesInSqlString($  sql);          if(!is_array($  params) || empty($  params)){             return $  this->mysqli->query($  sql);         }         if($  bind_types == ''){             $  bind_types = str_repeat("s", count($  params));         }         $  stmt = $  this->mysqli->prepare($  sql);         $  stmt->bind_param($  bind_types, ...$  params);         $  stmt->execute();         return $  stmt->get_result();     }      /**      * Replaces curly brace table names with their actual, prefixed name      * Eg "SELECT * from {table_name}" => "SELECT * from wp_table_name"      * @param string $  sql      * @return string      */     private function prefixTableNamesInSqlString($  sql)     {         return str_replace(["{", "}"], [$  this->wpdb->prefix, ""], $  sql);     }  } 

This would allow me to use real prepared queries, and have nice readable code like:

<?php $  sql = "             SELECT DISTINCT c.ID AS course_id, c.post_title AS course_title             FROM {posts} c             JOIN {tmsc_course_product} cp             ON = cp.course_id             WHERE cp.product_id IN(?,?)         "; //Yes, i know DB::run() can return bool! Out of scope for this question $  courses = $  db->run($  sql, [57,4761])->fetch_all(MYSQLI_ASSOC);  

My only concern is if this is going to have any knock on effects with wpdb and its dependents. The wpdb class is not exactly easy to read, and appears holds a lot of state.