WordPress.org

Make WordPress Core

Changeset 31349


Ignore:
Timestamp:
02/06/2015 04:50:19 AM (5 years ago)
Author:
pento
Message:

WPDB: If a site is using the utf8 charset, and their version of MySQL supports utf8mb4, auto-upgrade them to utf8mb4.

This patch also resizes some indexes, to allow for the 767 byte index size limit in standard MySQL installs.

See #21212

Location:
trunk
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/includes/schema.php

    r30742 r31349  
    4444    // Engage multisite if in the middle of turning it on from network.php.
    4545    $is_multisite = is_multisite() || ( defined( 'WP_INSTALLING_NETWORK' ) && WP_INSTALLING_NETWORK );
     46
     47    /*
     48     * Indexes have a maximum size of 767 bytes. Historically, we haven't need to be concerned about that.
     49     * As of 4.2, however, we moved to utf8mb4, which uses 4 bytes per character. This means that an index which
     50     * used to have room for floor(767/3) = 255 characters, now only has room for floor(767/4) = 191 characters.
     51     */
     52    $max_index_length = 191;
    4653
    4754    // Blog specific tables.
     
    5259 term_group bigint(10) NOT NULL default 0,
    5360 PRIMARY KEY  (term_id),
    54  KEY slug (slug),
    55  KEY name (name)
     61 KEY slug (slug($max_index_length)),
     62 KEY name (name($max_index_length))
    5663) $charset_collate;
    5764CREATE TABLE $wpdb->term_taxonomy (
     
    8087  PRIMARY KEY  (meta_id),
    8188  KEY comment_id (comment_id),
    82   KEY meta_key (meta_key)
     89  KEY meta_key (meta_key($max_index_length))
    8390) $charset_collate;
    8491CREATE TABLE $wpdb->comments (
     
    137144  PRIMARY KEY  (meta_id),
    138145  KEY post_id (post_id),
    139   KEY meta_key (meta_key)
     146  KEY meta_key (meta_key($max_index_length))
    140147) $charset_collate;
    141148CREATE TABLE $wpdb->posts (
     
    164171  comment_count bigint(20) NOT NULL default '0',
    165172  PRIMARY KEY  (ID),
    166   KEY post_name (post_name),
     173  KEY post_name (post_name($max_index_length)),
    167174  KEY type_status_date (post_type,post_status,post_date,ID),
    168175  KEY post_parent (post_parent),
     
    214221  PRIMARY KEY  (umeta_id),
    215222  KEY user_id (user_id),
    216   KEY meta_key (meta_key)
     223  KEY meta_key (meta_key($max_index_length))
    217224) $charset_collate;\n";
    218225
     
    262269  path varchar(100) NOT NULL default '',
    263270  PRIMARY KEY  (id),
    264   KEY domain (domain,path)
     271  KEY domain (domain(140),path(51))
    265272) $charset_collate;
    266273CREATE TABLE $wpdb->sitemeta (
     
    270277  meta_value longtext,
    271278  PRIMARY KEY  (meta_id),
    272   KEY meta_key (meta_key),
     279  KEY meta_key (meta_key($max_index_length)),
    273280  KEY site_id (site_id)
    274281) $charset_collate;
     
    289296  KEY user_email (user_email),
    290297  KEY user_login_email (user_login,user_email),
    291   KEY domain_path (domain,path)
     298  KEY domain_path (domain(140),path(51))
    292299) $charset_collate;";
    293300
  • trunk/src/wp-admin/includes/upgrade.php

    r31246 r31349  
    519519    if ( $wp_current_db_version < 29630 )
    520520        upgrade_400();
     521
     522    if ( $wp_current_db_version < 31349 )
     523        upgrade_420();
    521524
    522525    maybe_disable_link_manager();
     
    14081411
    14091412/**
     1413 * Execute changes made in WordPress 4.2.0.
     1414 *
     1415 * @since 4.2.0
     1416 */
     1417function upgrade_420() {
     1418    global $wp_current_db_version, $wpdb;
     1419
     1420    if ( $wp_current_db_version < 31349 && $wpdb->charset === 'utf8mb4' ) {
     1421        if ( is_multisite() ) {
     1422            $tables = $wpdb->tables( 'blog' );
     1423        } else {
     1424            $tables = $wpdb->tables( 'all' );
     1425        }
     1426
     1427        foreach ( $tables as $table ) {
     1428            maybe_convert_table_to_utf8mb4( $table );
     1429        }
     1430    }
     1431}
     1432
     1433/**
    14101434 * Executes network-level upgrade routines.
    14111435 *
     
    15031527        }
    15041528    }
     1529
     1530    // 4.2
     1531    if ( $wp_current_db_version < 31349 && $wpdb->charset === 'utf8mb4' ) {
     1532        if ( ! ( defined( 'DO_NOT_UPGRADE_GLOBAL_TABLES' ) && DO_NOT_UPGRADE_GLOBAL_TABLES ) ) {
     1533            $wpdb->query( "ALTER TABLE $wpdb->site DROP INDEX domain, ADD INDEX domain(domain(140),path(51))" );
     1534            $wpdb->query( "ALTER TABLE $wpdb->sitemeta DROP INDEX meta_key, ADD INDEX meta_key(meta_key(191))" );
     1535            $wpdb->query( "ALTER TABLE $wpdb->signups DROP INDEX domain, ADD INDEX domain(domain(140),path(51))" );
     1536
     1537            $tables = $wpdb->tables( 'global' );
     1538
     1539            foreach ( $tables as $table ) {
     1540                maybe_convert_table_to_utf8mb4( $table );
     1541            }
     1542        }
     1543    }
    15051544}
    15061545
     
    16061645    }
    16071646    return false;
     1647}
     1648
     1649/**
     1650 * If a table only contains utf8 or utf8mb4 columns, convert it to utf8mb4.
     1651 *
     1652 * @since 4.2.0
     1653 *
     1654 * @param string $table The table to convert.
     1655 * @return bool true if the table was converted, false if it wasn't.
     1656 */
     1657function maybe_convert_table_to_utf8mb4( $table ) {
     1658    global $wpdb;
     1659
     1660    $results = $wpdb->get_results( "SHOW FULL COLUMNS FROM `$table`" );
     1661    if ( ! $results ) {
     1662        return false;
     1663    }
     1664
     1665    $has_utf8 = false;
     1666    foreach ( $results as $column ) {
     1667        if ( $column->Collation ) {
     1668            if ( 'utf8' === $column->Collation ) {
     1669                $has_utf8 = true;
     1670            } elseif ( 'utf8mb4' !== $column->Collation ) {
     1671                // Don't upgrade tables that have non-utf8 columns.
     1672                return false;
     1673            }
     1674        }
     1675    }
     1676
     1677    if ( ! $has_utf8 ) {
     1678        // Don't bother upgrading tables that don't have utf8 columns.
     1679        return false;
     1680    }
     1681
     1682    return $wpdb->query( "ALTER TABLE $table CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci" );
    16081683}
    16091684
     
    22852360        $wpdb->query( "ALTER TABLE $wpdb->terms DROP INDEX slug" );
    22862361    }
     2362
     2363    // Upgrade versions prior to 4.2.
     2364    if ( $wp_current_db_version < 31349 ) {
     2365        // So that we can change tables to utf8mb4, we need to shorten the index lengths to less than 767 bytes
     2366        $wpdb->query( "ALTER TABLE $wpdb->usermeta DROP INDEX meta_key, ADD INDEX meta_key(meta_key(191))" );
     2367        $wpdb->query( "ALTER TABLE $wpdb->terms DROP INDEX slug, ADD INDEX slug(slug(191))" );
     2368        $wpdb->query( "ALTER TABLE $wpdb->terms DROP INDEX name, ADD INDEX name(name(191))" );
     2369        $wpdb->query( "ALTER TABLE $wpdb->commentmeta DROP INDEX meta_key, ADD INDEX meta_key(meta_key(191))" );
     2370        $wpdb->query( "ALTER TABLE $wpdb->postmeta DROP INDEX meta_key, ADD INDEX meta_key(meta_key(191))" );
     2371        $wpdb->query( "ALTER TABLE $wpdb->posts DROP INDEX post_name, ADD INDEX post_name(post_name(191))" );
     2372    }
    22872373}
    22882374
  • trunk/src/wp-admin/setup-config.php

    r30843 r31349  
    281281                $config_file[ $line_num ] = "define('" . $constant . "'," . $padding . "'" . addcslashes( constant( $constant ), "\\'" ) . "');\r\n";
    282282                break;
     283            case 'DB_CHARSET'  :
     284                if ( 'utf8mb4' === $wpdb->charset || ( ! $wpdb->charset && $wpdb->has_cap( 'utf8mb4' ) ) ) {
     285                    $config_file[ $line_num ] = "define('" . $constant . "'," . $padding . "'utf8mb4');\r\n";
     286                }
     287                break;
    283288            case 'AUTH_KEY'         :
    284289            case 'SECURE_AUTH_KEY'  :
  • trunk/src/wp-includes/version.php

    r31007 r31349  
    1212 * @global int $wp_db_version
    1313 */
    14 $wp_db_version = 30133;
     14$wp_db_version = 31349;
    1515
    1616/**
  • trunk/src/wp-includes/wp-db.php

    r31294 r31349  
    625625        }
    626626
    627         $this->init_charset();
    628 
    629627        $this->dbuser = $dbuser;
    630628        $this->dbpassword = $dbpassword;
     
    718716        if ( function_exists('is_multisite') && is_multisite() ) {
    719717            $this->charset = 'utf8';
    720             if ( defined( 'DB_COLLATE' ) && DB_COLLATE )
     718            if ( defined( 'DB_COLLATE' ) && DB_COLLATE ) {
    721719                $this->collate = DB_COLLATE;
    722             else
     720            } else {
    723721                $this->collate = 'utf8_general_ci';
     722            }
    724723        } elseif ( defined( 'DB_COLLATE' ) ) {
    725724            $this->collate = DB_COLLATE;
    726725        }
    727726
    728         if ( defined( 'DB_CHARSET' ) )
     727        if ( defined( 'DB_CHARSET' ) ) {
    729728            $this->charset = DB_CHARSET;
     729        }
     730
     731        if ( ( $this->use_mysqli && ! ( $this->dbh instanceof mysqli ) )
     732          || ( empty( $this->dbh ) || ! ( $this->dbh instanceof mysqli ) ) ) {
     733            return;
     734        }
     735
     736        if ( 'utf8' === $this->charset && $this->has_cap( 'utf8mb4' ) ) {
     737            $this->charset = 'utf8mb4';
     738        }
     739
     740        if ( 'utf8mb4' === $this->charset && ( ! $this->collate || stripos( $this->collate, 'utf8_' ) === 0 ) ) {
     741            $this->collate = 'utf8mb4_unicode_ci';
     742        }
    730743    }
    731744
     
    14771490            return false;
    14781491        } elseif ( $this->dbh ) {
     1492            if ( ! $this->has_connected ) {
     1493                $this->init_charset();
     1494            }
     1495
    14791496            $this->has_connected = true;
     1497
    14801498            $this->set_charset( $this->dbh );
     1499
    14811500            $this->ready = true;
    14821501            $this->set_sql_mode();
     
    22502269     *
    22512270     * @since 4.2.0
    2252      * @access protected
     2271     * @access public
    22532272     *
    22542273     * @param string $table  Table name.
     
    22572276     *               character set. {@see WP_Error} object if there was an error.
    22582277     */
    2259     protected function get_col_charset( $table, $column ) {
     2278    public function get_col_charset( $table, $column ) {
    22602279        $tablekey = strtolower( $table );
    22612280        $columnkey = strtolower( $column );
     
    23572376            'ujis'    => 'EUC-JP',
    23582377            'utf32'   => 'UTF-32',
    2359             'utf8mb4' => 'UTF-8',
    23602378        );
    23612379
     
    23922410            }
    23932411
    2394             // utf8(mb3) can be handled by regex, which is a bunch faster than a DB lookup.
    2395             if ( 'utf8' === $charset || 'utf8mb3' === $charset ) {
     2412            // utf8 can be handled by regex, which is a bunch faster than a DB lookup.
     2413            if ( 'utf8' === $charset || 'utf8mb3' === $charset || 'utf8mb4' === $charset ) {
    23962414                $regex = '/
    23972415                    (
     
    24012419                        |   [\xE1-\xEC][\x80-\xBF]{2}
    24022420                        |   \xED[\x80-\x9F][\x80-\xBF]
    2403                         |   [\xEE-\xEF][\x80-\xBF]{2}
    2404                         ){1,50}                          # ...one or more times
     2421                        |   [\xEE-\xEF][\x80-\xBF]{2}';
     2422
     2423                if ( 'utf8mb4' === $charset) {
     2424                    $regex .= '
     2425                        |    \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences   11110xxx 10xxxxxx * 3
     2426                        |    [\xF1-\xF3][\x80-\xBF]{3}
     2427                        |    \xF4[\x80-\x8F][\x80-\xBF]{2}
     2428                    ';
     2429                }
     2430
     2431                $regex .= '){1,50}                          # ...one or more times
    24052432                    )
    24062433                    | .                                  # anything else
  • trunk/tests/phpunit/tests/db/charset.php

    r30699 r31349  
    131131
    132132    /**
    133      * @ ticket 21212
     133     * @ticket 21212
    134134     */
    135135    function test_process_fields_failure() {
    136136        global $wpdb;
    137         $data = array( 'post_content' => "H€llo\xf0\x9f\x98\x88World¢" );
     137        // \xf0\xff\xff\xff is invalid in utf8 and utf8mb4.
     138        $data = array( 'post_content' => "H€llo\xf0\xff\xff\xffWorld¢" );
    138139        $this->assertFalse( self::$_wpdb->process_fields( $wpdb->posts, $data, null ) );
    139140    }
     
    437438    function test_invalid_characters_in_query() {
    438439        global $wpdb;
    439         $this->assertFalse( $wpdb->query( "INSERT INTO {$wpdb->posts} (post_content) VALUES ('foo\xf0\x9f\x98\x88bar')" ) );
     440        $this->assertFalse( $wpdb->query( "INSERT INTO {$wpdb->posts} (post_content) VALUES ('foo\xf0\xff\xff\xffbar')" ) );
    440441    }
    441442}
Note: See TracChangeset for help on using the changeset viewer.