* Multisite Sitewide Terms table.
* Multisite Site Metadata table.
* Format specifiers for DB columns.
* Columns not listed here default to %s. Initialized during WP load.
* Keys are column names, values are format types: 'ID' => '%d'.
* @see wp_set_wpdb_vars()
public $field_types = array();
* Database table columns charset.
* Database table columns collate.
* - `mysqli` instance during normal operation
* - `null` if the connection is yet to be made or has been closed
* - `false` if the connection has failed
* A textual description of the last query/get_row/get_var call.
* Whether MySQL is used as the database engine.
* Set in wpdb::db_connect() to true, by default. This is used when checking
* against the required MySQL version for WordPress. Normally, a replacement
* database drop-in (db.php) will skip these checks, but setting this to true
* will force the checks to occur.
* A list of incompatible SQL modes.
protected $incompatible_modes = array(
* Backward compatibility, where wpdb::prepare() has not quoted formatted/argnum placeholders.
* This is often used for table/field names (before %i was supported), and sometimes string formatting, e.g.
* $wpdb->prepare( 'WHERE `%1$s` = "%2$s something %3$s" OR %1$s = "%4$-10s"', 'field_1', 'a', 'b', 'c' );
* But it's risky, e.g. forgetting to add quotes, resulting in SQL Injection vulnerabilities:
* $wpdb->prepare( 'WHERE (id = %1s) OR (id = %2$s)', $_GET['id'], $_GET['id'] ); // ?id=id
* This feature is preserved while plugin authors update their code to use safer approaches:
* $wpdb->prepare( 'WHERE %1s = %s', $_GET['key'], $_GET['value'] ); // WHERE a`b = 'value'
* $wpdb->prepare( 'WHERE `%1$s` = "%2$s"', $_GET['key'], $_GET['value'] ); // WHERE `a`b` = "value"
* $wpdb->prepare( 'WHERE %i = %s', $_GET['key'], $_GET['value'] ); // WHERE `a``b` = 'value'
* While changing to false will be fine for queries not using formatted/argnum placeholders,
* any remaining cases are most likely going to result in SQL errors (good, in a way):
* $wpdb->prepare( 'WHERE %1$s = "%2$-10s"', 'my_field', 'my_value' );
* true = WHERE my_field = "my_value "
* false = WHERE 'my_field' = "'my_value '"
* But there may be some queries that result in an SQL Injection vulnerability:
* $wpdb->prepare( 'WHERE id = %1$s', $_GET['id'] ); // ?id=id
* So there may need to be a `_doing_it_wrong()` phase, after we know everyone can use
* identifier placeholders (%i), but before this feature is disabled or removed.
private $allow_unsafe_unquoted_parameters = true;
* Whether to use the mysqli extension over mysql. This is no longer used as the mysql
* extension is no longer supported.
* @since 6.4.0 This property was removed.
* @since 6.4.1 This property was reinstated and its default value was changed to true.
* The property is no longer used in core but may be accessed externally.
private $use_mysqli = true;
* Whether we've managed to successfully connect at some point.
private $has_connected = false;
* Time when the last query was performed.
* Only set when `SAVEQUERIES` is defined and truthy.
public $time_start = null;
* The last SQL error that was encountered.
* Connects to the database server and selects a database.
* Does the actual setting up
* of the class properties and connection to the database.
* @link https://core.trac.wordpress.org/ticket/3354
* @param string $dbuser Database user.
* @param string $dbpassword Database password.
* @param string $dbname Database name.
* @param string $dbhost Database host.
public function __construct(
if ( WP_DEBUG && WP_DEBUG_DISPLAY ) {
$this->dbpassword = $dbpassword;
// wp-config.php creation will manually connect when ready.
if ( defined( 'WP_SETUP_CONFIG' ) ) {
* Makes private properties readable for backward compatibility.
* @param string $name The private member to get, and optionally process.
* @return mixed The private member.
public function __get( $name ) {
if ( 'col_info' === $name ) {
* Makes private properties settable for backward compatibility.
* @param string $name The private member to set.
* @param mixed $value The value to set.
public function __set( $name, $value ) {
$protected_members = array(
'allow_unsafe_unquoted_parameters',
if ( in_array( $name, $protected_members, true ) ) {
* Makes private properties check-able for backward compatibility.
* @param string $name The private member to check.
* @return bool If the member is set or not.
public function __isset( $name ) {
return isset( $this->$name );
* Makes private properties un-settable for backward compatibility.
* @param string $name The private member to unset.
public function __unset( $name ) {
* Sets $this->charset and $this->collate.
public function init_charset() {
if ( function_exists( 'is_multisite' ) && is_multisite() ) {
if ( defined( 'DB_COLLATE' ) && DB_COLLATE ) {
$collate = 'utf8_general_ci';
} elseif ( defined( 'DB_COLLATE' ) ) {
if ( defined( 'DB_CHARSET' ) ) {
$charset_collate = $this->determine_charset( $charset, $collate );
$this->charset = $charset_collate['charset'];
$this->collate = $charset_collate['collate'];
* Determines the best charset and collation to use given a charset and collation.
* For example, when able, utf8mb4 should be used instead of utf8.
* @param string $charset The character set to check.
* @param string $collate The collation to check.
* The most appropriate character set and collation to use.
* @type string $charset Character set.
* @type string $collate Collation.
public function determine_charset( $charset, $collate ) {
if ( ( ! ( $this->dbh instanceof mysqli ) ) || empty( $this->dbh ) ) {
return compact( 'charset', 'collate' );
if ( 'utf8' === $charset ) {
if ( 'utf8mb4' === $charset ) {
// _general_ is outdated, so we can upgrade it to _unicode_, instead.
if ( ! $collate || 'utf8_general_ci' === $collate ) {
$collate = 'utf8mb4_unicode_ci';
$collate = str_replace( 'utf8_', 'utf8mb4_', $collate );
// _unicode_520_ is a better collation, we should use that when it's available.
if ( $this->has_cap( 'utf8mb4_520' ) && 'utf8mb4_unicode_ci' === $collate ) {
$collate = 'utf8mb4_unicode_520_ci';
return compact( 'charset', 'collate' );
* Sets the connection's character set.
* @param mysqli $dbh The connection returned by `mysqli_connect()`.
* @param string $charset Optional. The character set. Default null.
* @param string $collate Optional. The collation. Default null.
public function set_charset( $dbh, $charset = null, $collate = null ) {
if ( ! isset( $charset ) ) {
$charset = $this->charset;
if ( ! isset( $collate ) ) {
$collate = $this->collate;
if ( $this->has_cap( 'collation' ) && ! empty( $charset ) ) {
$set_charset_succeeded = true;
if ( function_exists( 'mysqli_set_charset' ) && $this->has_cap( 'set_charset' ) ) {
$set_charset_succeeded = mysqli_set_charset( $dbh, $charset );
if ( $set_charset_succeeded ) {
$query = $this->prepare( 'SET NAMES %s', $charset );
if ( ! empty( $collate ) ) {
$query .= $this->prepare( ' COLLATE %s', $collate );
mysqli_query( $dbh, $query );
* Changes the current SQL mode, and ensures its WordPress compatibility.
* If no modes are passed, it will ensure the current SQL server modes are compatible.
* @param array $modes Optional. A list of SQL modes to set. Default empty array.
public function set_sql_mode( $modes = array() ) {
$res = mysqli_query( $this->dbh, 'SELECT @@SESSION.sql_mode' );
$modes_array = mysqli_fetch_array( $res );
if ( empty( $modes_array[0] ) ) {
$modes_str = $modes_array[0];
if ( empty( $modes_str ) ) {
$modes = explode( ',', $modes_str );
$modes = array_change_key_case( $modes, CASE_UPPER );
* Filters the list of incompatible SQL modes to exclude.
* @param array $incompatible_modes An array of incompatible modes.
$incompatible_modes = (array) apply_filters( 'incompatible_sql_modes', $this->incompatible_modes );
foreach ( $modes as $i => $mode ) {
if ( in_array( $mode, $incompatible_modes, true ) ) {
$modes_str = implode( ',', $modes );
mysqli_query( $this->dbh, "SET SESSION sql_mode='$modes_str'" );
* Sets the table prefix for the WordPress tables.
* @param string $prefix Alphanumeric name for the new prefix.
* @param bool $set_table_names Optional. Whether the table names, e.g. wpdb::$posts,