$wpdb->query( "ALTER TABLE `$table` DROP INDEX `$index`" );
// Now we need to take out all the extra ones we may have created.
for ( $i = 0; $i < 25; $i++ ) {
$wpdb->query( "ALTER TABLE `$table` DROP INDEX `{$index}_$i`" );
* Adds an index to a specified table.
* @global wpdb $wpdb WordPress database abstraction object.
* @param string $table Database table name.
* @param string $index Database table index column.
* @return true True, when done with execution.
function add_clean_index( $table, $index ) {
drop_index( $table, $index );
$wpdb->query( "ALTER TABLE `$table` ADD INDEX ( `$index` )" );
* Adds column to a database table, if it doesn't already exist.
* @global wpdb $wpdb WordPress database abstraction object.
* @param string $table_name Database table name.
* @param string $column_name Table column name.
* @param string $create_ddl SQL statement to add column.
* @return bool True on success or if the column already exists. False on failure.
function maybe_add_column( $table_name, $column_name, $create_ddl ) {
foreach ( $wpdb->get_col( "DESC $table_name", 0 ) as $column ) {
if ( $column === $column_name ) {
// Didn't find it, so try to create it.
$wpdb->query( $create_ddl );
// We cannot directly tell that whether this succeeded!
foreach ( $wpdb->get_col( "DESC $table_name", 0 ) as $column ) {
if ( $column === $column_name ) {
* If a table only contains utf8 or utf8mb4 columns, convert it to utf8mb4.
* @global wpdb $wpdb WordPress database abstraction object.
* @param string $table The table to convert.
* @return bool True if the table was converted, false if it wasn't.
function maybe_convert_table_to_utf8mb4( $table ) {
$results = $wpdb->get_results( "SHOW FULL COLUMNS FROM `$table`" );
foreach ( $results as $column ) {
if ( $column->Collation ) {
list( $charset ) = explode( '_', $column->Collation );
$charset = strtolower( $charset );
if ( 'utf8' !== $charset && 'utf8mb4' !== $charset ) {
// Don't upgrade tables that have non-utf8 columns.
$table_details = $wpdb->get_row( "SHOW TABLE STATUS LIKE '$table'" );
if ( ! $table_details ) {
list( $table_charset ) = explode( '_', $table_details->Collation );
$table_charset = strtolower( $table_charset );
if ( 'utf8mb4' === $table_charset ) {
return $wpdb->query( "ALTER TABLE $table CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci" );
* Retrieve all options as it was for 1.2.
* @global wpdb $wpdb WordPress database abstraction object.
* @return stdClass List of options.
function get_alloptions_110() {
$all_options = new stdClass;
$options = $wpdb->get_results( "SELECT option_name, option_value FROM $wpdb->options" );
foreach ( $options as $option ) {
if ( 'siteurl' === $option->option_name || 'home' === $option->option_name || 'category_base' === $option->option_name ) {
$option->option_value = untrailingslashit( $option->option_value );
$all_options->{$option->option_name} = stripslashes( $option->option_value );
* Utility version of get_option that is private to installation/upgrade.
* @global wpdb $wpdb WordPress database abstraction object.
* @param string $setting Option name.
function __get_option( $setting ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore
if ( 'home' === $setting && defined( 'WP_HOME' ) ) {
return untrailingslashit( WP_HOME );
if ( 'siteurl' === $setting && defined( 'WP_SITEURL' ) ) {
return untrailingslashit( WP_SITEURL );
$option = $wpdb->get_var( $wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = %s", $setting ) );
if ( 'home' === $setting && ! $option ) {
return __get_option( 'siteurl' );
if ( in_array( $setting, array( 'siteurl', 'home', 'category_base', 'tag_base' ), true ) ) {
$option = untrailingslashit( $option );
return maybe_unserialize( $option );
* Filters for content to remove unnecessary slashes.
* @param string $content The content to modify.
* @return string The de-slashed content.
function deslash( $content ) {
// Note: \\\ inside a regex denotes a single backslash.
* Replace one or more backslashes followed by a single quote with
$content = preg_replace( "/\\\+'/", "'", $content );
* Replace one or more backslashes followed by a double quote with
$content = preg_replace( '/\\\+"/', '"', $content );
// Replace one or more backslashes with one backslash.
$content = preg_replace( '/\\\+/', '\\', $content );
* Modifies the database based on specified SQL statements.
* Useful for creating new tables and updating existing tables to a new structure.
* @global wpdb $wpdb WordPress database abstraction object.
* @param string[]|string $queries Optional. The query to run. Can be multiple queries
* in an array, or a string of queries separated by
* semicolons. Default empty string.
* @param bool $execute Optional. Whether or not to execute the query right away.
* @return array Strings containing the results of the various update queries.
function dbDelta( $queries = '', $execute = true ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionNameInvalid
if ( in_array( $queries, array( '', 'all', 'blog', 'global', 'ms_global' ), true ) ) {
$queries = wp_get_db_schema( $queries );
// Separate individual queries into an array.
if ( ! is_array( $queries ) ) {
$queries = explode( ';', $queries );
$queries = array_filter( $queries );
* Filters the dbDelta SQL queries.
* @param string[] $queries An array of dbDelta SQL queries.
$queries = apply_filters( 'dbdelta_queries', $queries );
$cqueries = array(); // Creation queries.
$iqueries = array(); // Insertion queries.
// Create a tablename index for an array ($cqueries) of queries.
foreach ( $queries as $qry ) {
if ( preg_match( '|CREATE TABLE ([^ ]*)|', $qry, $matches ) ) {
$cqueries[ trim( $matches[1], '`' ) ] = $qry;
$for_update[ $matches[1] ] = 'Created table ' . $matches[1];
} elseif ( preg_match( '|CREATE DATABASE ([^ ]*)|', $qry, $matches ) ) {
array_unshift( $cqueries, $qry );
} elseif ( preg_match( '|INSERT INTO ([^ ]*)|', $qry, $matches ) ) {
} elseif ( preg_match( '|UPDATE ([^ ]*)|', $qry, $matches ) ) {
// Unrecognized query type.
* Filters the dbDelta SQL queries for creating tables and/or databases.
* Queries filterable via this hook contain "CREATE TABLE" or "CREATE DATABASE".
* @param string[] $cqueries An array of dbDelta create SQL queries.
$cqueries = apply_filters( 'dbdelta_create_queries', $cqueries );
* Filters the dbDelta SQL queries for inserting or updating.
* Queries filterable via this hook contain "INSERT INTO" or "UPDATE".
* @param string[] $iqueries An array of dbDelta insert or update SQL queries.
$iqueries = apply_filters( 'dbdelta_insert_queries', $iqueries );
$text_fields = array( 'tinytext', 'text', 'mediumtext', 'longtext' );
$blob_fields = array( 'tinyblob', 'blob', 'mediumblob', 'longblob' );
$global_tables = $wpdb->tables( 'global' );
foreach ( $cqueries as $table => $qry ) {
// Upgrade global tables only for the main site. Don't upgrade at all if conditions are not optimal.
if ( in_array( $table, $global_tables, true ) && ! wp_should_upgrade_global_tables() ) {
unset( $cqueries[ $table ], $for_update[ $table ] );
// Fetch the table column structure from the database.
$suppress = $wpdb->suppress_errors();
$tablefields = $wpdb->get_results( "DESCRIBE {$table};" );
$wpdb->suppress_errors( $suppress );
// Clear the field and index arrays.
$indices_without_subparts = array();
// Get all of the field names in the query from between the parentheses.
preg_match( '|\((.*)\)|ms', $qry, $match2 );
$qryline = trim( $match2[1] );
// Separate field lines into an array.
$flds = explode( "\n", $qryline );
// For every field line specified in the query.
foreach ( $flds as $fld ) {
$fld = trim( $fld, " \t\n\r\0\x0B," ); // Default trim characters, plus ','.
// Extract the field name.
preg_match( '|^([^ ]*)|', $fld, $fvals );
$fieldname = trim( $fvals[1], '`' );
$fieldname_lowercased = strtolower( $fieldname );
// Verify the found field name.
switch ( $fieldname_lowercased ) {
* Normalize the index definition.
* This is done so the definition can be compared against the result of a
* `SHOW INDEX FROM $table_name` query which returns the current table
// Extract type, name and columns from the definition.
// phpcs:disable Squiz.Strings.ConcatenationSpacing.PaddingFound -- don't remove regex indentation
. '(?P<index_type>' // 1) Type of the index.
. 'PRIMARY\s+KEY|(?:UNIQUE|FULLTEXT|SPATIAL)\s+(?:KEY|INDEX)|KEY|INDEX'
. '\s+' // Followed by at least one white space character.
. '(?:' // Name of the index. Optional if type is PRIMARY KEY.
. '`?' // Name can be escaped with a backtick.
. '(?P<index_name>' // 2) Name of the index.
. '(?:[0-9a-zA-Z$_-]|[\xC2-\xDF][\x80-\xBF])+'
. '`?' // Name can be escaped with a backtick.
. '\s+' // Followed by at least one white space character.
. '\(' // Opening bracket for the columns.
. '.+?' // 3) Column names, index prefixes, and orders.
. '\)' // Closing bracket for the columns.
// Uppercase the index type and normalize space characters.
$index_type = strtoupper( preg_replace( '/\s+/', ' ', trim( $index_matches['index_type'] ) ) );
// 'INDEX' is a synonym for 'KEY', standardize on 'KEY'.
$index_type = str_replace( 'INDEX', 'KEY', $index_type );
// Escape the index name with backticks. An index for a primary key has no name.
$index_name = ( 'PRIMARY KEY' === $index_type ) ? '' : '`' . strtolower( $index_matches['index_name'] ) . '`';
// Parse the columns. Multiple columns are separated by a comma.
$index_columns = array_map( 'trim', explode( ',', $index_matches['index_columns'] ) );
$index_columns_without_subparts = $index_columns;
foreach ( $index_columns as $id => &$index_column ) {
// Extract column name and number of indexed characters (sub_part).
. '`?' // Name can be escaped with a backtick.
. '(?P<column_name>' // 1) Name of the column.
. '(?:[0-9a-zA-Z$_-]|[\xC2-\xDF][\x80-\xBF])+'
. '`?' // Name can be escaped with a backtick.
. '(?:' // Optional sub part.
. '\s*' // Optional white space character between name and opening bracket.
. '\(' // Opening bracket for the sub part.
. '\s*' // Optional white space character after opening bracket.
. '\d+' // 2) Number of indexed characters.
. '\s*' // Optional white space character before closing bracket.
. '\)' // Closing bracket for the sub part.
// Escape the column name with backticks.
$index_column = '`' . $index_column_matches['column_name'] . '`';
// We don't need to add the subpart to $index_columns_without_subparts
$index_columns_without_subparts[ $id ] = $index_column;
// Append the optional sup part with the number of indexed characters.
if ( isset( $index_column_matches['sub_part'] ) ) {
$index_column .= '(' . $index_column_matches['sub_part'] . ')';
// Build the normalized index definition and add it to the list of indices.
$indices[] = "{$index_type} {$index_name} (" . implode( ',', $index_columns ) . ')';
$indices_without_subparts[] = "{$index_type} {$index_name} (" . implode( ',', $index_columns_without_subparts ) . ')';
// Destroy no longer needed variables.
unset( $index_column, $index_column_matches, $index_matches, $index_type, $index_name, $index_columns, $index_columns_without_subparts );
// If it's a valid field, add it to the field array.
$cfields[ $fieldname_lowercased ] = $fld;
// For every field in the table.
foreach ( $tablefields as $tablefield ) {
$tablefield_field_lowercased = strtolower( $tablefield->Field );
$tablefield_type_lowercased = strtolower( $tablefield->Type );
// If the table field exists in the field array...
if ( array_key_exists( $tablefield_field_lowercased, $cfields ) ) {
// Get the field type from the query.
preg_match( '|`?' . $tablefield->Field . '`? ([^ ]*( unsigned)?)|i', $cfields[ $tablefield_field_lowercased ], $matches );
$fieldtype = $matches[1];
$fieldtype_lowercased = strtolower( $fieldtype );
// Is actual field type different from the field type in query?
if ( $tablefield->Type != $fieldtype ) {
if ( in_array( $fieldtype_lowercased, $text_fields, true ) && in_array( $tablefield_type_lowercased, $text_fields, true ) ) {
if ( array_search( $fieldtype_lowercased, $text_fields, true ) < array_search( $tablefield_type_lowercased, $text_fields, true ) ) {
if ( in_array( $fieldtype_lowercased, $blob_fields, true ) && in_array( $tablefield_type_lowercased, $blob_fields, true ) ) {
if ( array_search( $fieldtype_lowercased, $blob_fields, true ) < array_search( $tablefield_type_lowercased, $blob_fields, true ) ) {
// Add a query to change the column type.
$cqueries[] = "ALTER TABLE {$table} CHANGE COLUMN `{$tablefield->Field}` " . $cfields[ $tablefield_field_lowercased ];
$for_update[ $table . '.' . $tablefield->Field ] = "Changed type of {$table}.{$tablefield->Field} from {$tablefield->Type} to {$fieldtype}";
// Get the default value from the array.
if ( preg_match( "| DEFAULT '(.*?)'|i", $cfields[ $tablefield_field_lowercased ], $matches ) ) {
$default_value = $matches[1];
if ( $tablefield->Default != $default_value ) {
// Add a query to change the column's default value
$cqueries[] = "ALTER TABLE {$table} ALTER COLUMN `{$tablefield->Field}` SET DEFAULT '{$default_value}'";
$for_update[ $table . '.' . $tablefield->Field ] = "Changed default value of {$table}.{$tablefield->Field} from {$tablefield->Default} to {$default_value}";
// Remove the field from the array (so it's not added).
unset( $cfields[ $tablefield_field_lowercased ] );
// This field exists in the table, but not in the creation queries?
// For every remaining field specified for the table.
foreach ( $cfields as $fieldname => $fielddef ) {
// Push a query line into $cqueries that adds the field to that table.
$cqueries[] = "ALTER TABLE {$table} ADD COLUMN $fielddef";
$for_update[ $table . '.' . $fieldname ] = 'Added column ' . $table . '.' . $fieldname;
// Index stuff goes here. Fetch the table index structure from the database.
$tableindices = $wpdb->get_results( "SHOW INDEX FROM {$table};" );