Make WordPress Core

Changeset 37583


Ignore:
Timestamp:
05/27/2016 11:56:20 AM (8 years ago)
Author:
ocean90
Message:

Database: Normalize index definitions in dbDelta().

dbDelta() compares the index definitions against the result of SHOW INDEX FROM $table_name. This requires a specific format so indices are not unnecessarily re-created. This format wasn't ensured, until now.

  • Parse the raw index definition to extract the type, name and columns so a normalized definition can be built (#20263, #34873).
  • Standardize on uppercase types (#34871) and on 'KEY'. 'INDEX' is only a synonym for 'KEY'.
  • Escape index names with backticks (#20263).
  • Normalize columns: Ignore ASC and DESC definitions (#34959), remove whitespaces (#34869) and escape column names with backticks (#20263).
  • Add backticks to all index change queries (#20263).

Props ocean90, pento, kurtpayne.
Fixes #20263, #34869, #34871, #34873, #34959.

Location:
trunk
Files:
2 edited

Legend:

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

    r37574 r37583  
    21852185
    21862186        // For every field line specified in the query.
    2187         foreach ($flds as $fld) {
     2187        foreach ( $flds as $fld ) {
     2188            $fld = trim( $fld, " \t\n\r\0\x0B," ); // Default trim characters, plus ','.
    21882189
    21892190            // Extract the field name.
    2190             preg_match("|^([^ ]*)|", trim($fld), $fvals);
     2191            preg_match( '|^([^ ]*)|', $fld, $fvals );
    21912192            $fieldname = trim( $fvals[1], '`' );
    21922193            $fieldname_lowercased = strtolower( $fieldname );
     
    22032204                case 'spatial':
    22042205                    $validfield = false;
    2205                     $indices[] = trim(trim($fld), ", \n");
     2206
     2207                    /*
     2208                     * Normalize the index definition.
     2209                     *
     2210                     * This is done so the definition can be compared against the result of a
     2211                     * `SHOW INDEX FROM $table_name` query which returns the current table
     2212                     * index information.
     2213                     */
     2214
     2215                    // Extract type, name and columns from the definition.
     2216                    preg_match(
     2217                          '/^'
     2218                        .   '(?P<index_type>'             // 1) Type of the index.
     2219                        .       'PRIMARY\s+KEY|(?:UNIQUE|FULLTEXT|SPATIAL)\s+(?:KEY|INDEX)|KEY|INDEX'
     2220                        .   ')'
     2221                        .   '\s+'                         // Followed by at least one white space character.
     2222                        .   '(?:'                         // Name of the index. Optional if type is PRIMARY KEY.
     2223                        .       '`?'                      // Name can be escaped with a backtick.
     2224                        .           '(?P<index_name>'     // 2) Name of the index.
     2225                        .               '(?:[0-9a-zA-Z$_-]|[\xC2-\xDF][\x80-\xBF])+'
     2226                        .           ')'
     2227                        .       '`?'                      // Name can be escaped with a backtick.
     2228                        .       '\s+'                     // Followed by at least one white space character.
     2229                        .   ')*'
     2230                        .   '\('                          // Opening bracket for the columns.
     2231                        .       '(?P<index_columns>'
     2232                        .           '.+?'                 // 3) Column names, index prefixes, and orders.
     2233                        .       ')'
     2234                        .   '\)'                          // Closing bracket for the columns.
     2235                        . '$/im',
     2236                        $fld,
     2237                        $index_matches
     2238                    );
     2239
     2240                    // Uppercase the index type and normalize space characters.
     2241                    $index_type = strtoupper( preg_replace( '/\s+/', ' ', trim( $index_matches['index_type'] ) ) );
     2242
     2243                    // 'INDEX' is a synonym for 'KEY', standardize on 'KEY'.
     2244                    $index_type = str_replace( 'INDEX', 'KEY', $index_type );
     2245
     2246                    // Escape the index name with backticks. An index for a primary key has no name.
     2247                    $index_name = ( 'PRIMARY KEY' === $index_type ) ? '' : '`' . $index_matches['index_name'] . '`';
     2248
     2249                    // Parse the columns. Multiple columns are separated by a comma.
     2250                    $index_columns = array_map( 'trim', explode( ',', $index_matches['index_columns'] ) );
     2251
     2252                    // Normalize columns.
     2253                    foreach ( $index_columns as &$index_column ) {
     2254                        // Extract column name and number of indexed characters (sub_part).
     2255                        preg_match(
     2256                              '/'
     2257                            .   '`?'                      // Name can be escaped with a backtick.
     2258                            .       '(?P<column_name>'    // 1) Name of the column.
     2259                            .           '(?:[0-9a-zA-Z$_-]|[\xC2-\xDF][\x80-\xBF])+'
     2260                            .       ')'
     2261                            .   '`?'                      // Name can be escaped with a backtick.
     2262                            .   '(?:'                     // Optional sub part.
     2263                            .       '\s*'                 // Optional white space character between name and opening bracket.
     2264                            .       '\('                  // Opening bracket for the sub part.
     2265                            .           '\s*'             // Optional white space character after opening bracket.
     2266                            .           '(?P<sub_part>'
     2267                            .               '\d+'         // 2) Number of indexed characters.
     2268                            .           ')'
     2269                            .           '\s*'             // Optional white space character before closing bracket.
     2270                            .        '\)'                 // Closing bracket for the sub part.
     2271                            .   ')?'
     2272                            . '/',
     2273                            $index_column,
     2274                            $index_column_matches
     2275                        );
     2276
     2277                        // Escape the column name with backticks.
     2278                        $index_column = '`' . $index_column_matches['column_name'] . '`';
     2279
     2280                        // Append the optional sup part with the number of indexed characters.
     2281                        if ( isset( $index_column_matches['sub_part'] ) ) {
     2282                            $index_column .= '(' . $index_column_matches['sub_part'] . ')';
     2283                        }
     2284                    }
     2285
     2286                    // Build the normalized index definition and add it to the list of indices.
     2287                    $indices[] = "{$index_type} {$index_name} (" . implode( ',', $index_columns ) . ")";
     2288
     2289                    // Destroy no longer needed variables.
     2290                    unset( $index_column, $index_column_matches, $index_matches, $index_type, $index_name, $index_columns );
     2291
    22062292                    break;
    22072293            }
    2208             $fld = trim( $fld );
    22092294
    22102295            // If it's a valid field, add it to the field array.
    22112296            if ( $validfield ) {
    2212                 $cfields[ $fieldname_lowercased ] = trim( $fld, ", \n" );
     2297                $cfields[ $fieldname_lowercased ] = $fld;
    22132298            }
    22142299        }
     
    22442329                    if ( $do_change ) {
    22452330                        // Add a query to change the column type.
    2246                         $cqueries[] = "ALTER TABLE {$table} CHANGE COLUMN {$tablefield->Field} " . $cfields[ $tablefield_field_lowercased ];
     2331                        $cqueries[] = "ALTER TABLE {$table} CHANGE COLUMN `{$tablefield->Field}` " . $cfields[ $tablefield_field_lowercased ];
    22472332                        $for_update[$table.'.'.$tablefield->Field] = "Changed type of {$table}.{$tablefield->Field} from {$tablefield->Type} to {$fieldtype}";
    22482333                    }
     
    22542339                    if ($tablefield->Default != $default_value) {
    22552340                        // Add a query to change the column's default value
    2256                         $cqueries[] = "ALTER TABLE {$table} ALTER COLUMN {$tablefield->Field} SET DEFAULT '{$default_value}'";
     2341                        $cqueries[] = "ALTER TABLE {$table} ALTER COLUMN `{$tablefield->Field}` SET DEFAULT '{$default_value}'";
    22572342                        $for_update[$table.'.'.$tablefield->Field] = "Changed default value of {$table}.{$tablefield->Field} from {$tablefield->Default} to {$default_value}";
    22582343                    }
     
    23072392                }
    23082393                $index_string .= 'KEY ';
    2309                 if ($index_name != 'PRIMARY') {
    2310                     $index_string .= $index_name;
     2394                if ( 'PRIMARY' !== $index_name  ) {
     2395                    $index_string .= '`' . $index_name . '`';
    23112396                }
    23122397                $index_columns = '';
     
    23142399                // For each column in the index.
    23152400                foreach ($index_data['columns'] as $column_data) {
    2316                     if ($index_columns != '') $index_columns .= ',';
     2401                    if ( $index_columns != '' ) {
     2402                        $index_columns .= ',';
     2403                    }
    23172404
    23182405                    // Add the field to the column list string.
    2319                     $index_columns .= $column_data['fieldname'];
     2406                    $index_columns .= '`' . $column_data['fieldname'] . '`';
    23202407                    if ($column_data['subpart'] != '') {
    23212408                        $index_columns .= '('.$column_data['subpart'].')';
  • trunk/tests/phpunit/tests/dbdelta.php

    r37574 r37583  
    532532        $this->assertSame( array(
    533533            "{$wpdb->prefix}spatial_index_test.spatial_value2" => "Added column {$wpdb->prefix}spatial_index_test.spatial_value2",
    534             "Added index {$wpdb->prefix}spatial_index_test SPATIAL KEY spatial_key2 (spatial_value2)"
     534            "Added index {$wpdb->prefix}spatial_index_test SPATIAL KEY `spatial_key2` (`spatial_value2`)"
    535535            ), $updates );
    536536
    537537        $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}spatial_index_test" );
    538538    }
     539
     540    /**
     541     * @ticket 20263
     542     */
     543    function test_query_with_backticks_does_not_cause_a_query_to_alter_all_columns_and_indices_to_run_even_if_none_have_changed() {
     544        global $wpdb;
     545
     546        $schema = "
     547            CREATE TABLE {$wpdb->prefix}dbdelta_test2 (
     548                `id` bigint(20) NOT NULL AUTO_INCREMENT,
     549                `references` varchar(255) NOT NULL,
     550                PRIMARY KEY  (`id`),
     551                KEY `compound_key` (`id`,`references`)
     552            )
     553        ";
     554
     555        $wpdb->query( $schema );
     556
     557        $updates = dbDelta( $schema );
     558
     559        $table_indices = $wpdb->get_results( "SHOW INDEX FROM {$wpdb->prefix}dbdelta_test2" );
     560        $compound_key_index = wp_list_filter( $table_indices, array( 'Key_name' => 'compound_key' ) );
     561
     562        $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}dbdelta_test2" );
     563
     564        $this->assertCount( 2, $compound_key_index );
     565        $this->assertEmpty( $updates );
     566    }
     567
     568    /**
     569     * @ticket 20263
     570     */
     571    function test_index_with_a_reserved_keyword_can_be_created() {
     572        global $wpdb;
     573
     574        $updates = dbDelta(
     575            "
     576            CREATE TABLE {$wpdb->prefix}dbdelta_test (
     577                id bigint(20) NOT NULL AUTO_INCREMENT,
     578                column_1 varchar(255) NOT NULL,
     579                column_2 text,
     580                column_3 blob,
     581                `references` varchar(255) NOT NULL,
     582                PRIMARY KEY  (id),
     583                KEY key_1 (column_1),
     584                KEY compound_key (id , column_1),
     585                KEY compound_key2 (id,`references`),
     586                FULLTEXT KEY fulltext_key (column_1)
     587            ) ENGINE=MyISAM
     588            "
     589        );
     590
     591        $table_indices = $wpdb->get_results( "SHOW INDEX FROM {$wpdb->prefix}dbdelta_test" );
     592
     593        $this->assertCount( 2, wp_list_filter( $table_indices, array( 'Key_name' => 'compound_key2' ) , 'AND' ) );
     594
     595        $this->assertSame(
     596            array(
     597                "{$wpdb->prefix}dbdelta_test.references" => "Added column {$wpdb->prefix}dbdelta_test.references",
     598                0 => "Added index {$wpdb->prefix}dbdelta_test KEY `compound_key2` (`id`,`references`)",
     599            ),
     600            $updates
     601        );
     602    }
     603
     604    /**
     605     * @ticket 20263
     606     */
     607    function test_wp_get_db_schema_does_no_alter_queries_on_existing_install() {
     608        $updates = dbDelta( wp_get_db_schema() );
     609
     610        $this->assertEmpty( $updates );
     611    }
     612
     613    /**
     614     * @ticket 20263
     615     */
     616    function test_key_and_index_and_fulltext_key_and_fulltext_index_and_unique_key_and_unique_index_indicies() {
     617        global $wpdb;
     618
     619        $schema = "
     620            CREATE TABLE {$wpdb->prefix}dbdelta_test (
     621                id bigint(20) NOT NULL AUTO_INCREMENT,
     622                column_1 varchar(255) NOT NULL,
     623                column_2 text,
     624                column_3 blob,
     625                PRIMARY KEY  (id),
     626                KEY key_1 (column_1),
     627                KEY compound_key (id,column_1),
     628                FULLTEXT KEY fulltext_key (column_1),
     629                INDEX key_2 (column_1),
     630                UNIQUE KEY key_3 (column_1),
     631                UNIQUE INDEX key_4 (column_1),
     632                FULLTEXT INDEX key_5 (column_1),
     633            ) ENGINE=MyISAM
     634        ";
     635
     636        $creates = dbDelta( $schema );
     637        $this->assertSame(
     638            array(
     639                0 => "Added index {$wpdb->prefix}dbdelta_test KEY `key_2` (`column_1`)",
     640                1 => "Added index {$wpdb->prefix}dbdelta_test UNIQUE KEY `key_3` (`column_1`)",
     641                2 => "Added index {$wpdb->prefix}dbdelta_test UNIQUE KEY `key_4` (`column_1`)",
     642                3 => "Added index {$wpdb->prefix}dbdelta_test FULLTEXT KEY `key_5` (`column_1`)",
     643            ),
     644            $creates
     645        );
     646
     647        $updates = dbDelta( $schema );
     648        $this->assertEmpty( $updates );
     649    }
     650
     651    /**
     652     * @ticket 20263
     653     */
     654    function test_index_and_key_are_synonyms_and_do_not_recreate_indices() {
     655        global $wpdb;
     656
     657        $updates = dbDelta(
     658            "
     659            CREATE TABLE {$wpdb->prefix}dbdelta_test (
     660                id bigint(20) NOT NULL AUTO_INCREMENT,
     661                column_1 varchar(255) NOT NULL,
     662                column_2 text,
     663                column_3 blob,
     664                PRIMARY KEY  (id),
     665                INDEX key_1 (column_1),
     666                INDEX compound_key (id,column_1),
     667                FULLTEXT INDEX fulltext_key (column_1)
     668            ) ENGINE=MyISAM
     669            "
     670        );
     671
     672        $this->assertEmpty( $updates );
     673    }
     674
     675    /**
     676     * @ticket 20263
     677     */
     678    function test_indices_with_prefix_limits_are_created_and_do_not_recreate_indices() {
     679        global $wpdb;
     680
     681        $schema = "
     682            CREATE TABLE {$wpdb->prefix}dbdelta_test (
     683                id bigint(20) NOT NULL AUTO_INCREMENT,
     684                column_1 varchar(255) NOT NULL,
     685                column_2 text,
     686                column_3 blob,
     687                PRIMARY KEY  (id),
     688                KEY key_1 (column_1),
     689                KEY compound_key (id,column_1),
     690                FULLTEXT KEY fulltext_key (column_1),
     691                KEY key_2 (column_1(10)),
     692                KEY key_3 (column_2(100),column_1(10)),
     693            ) ENGINE=MyISAM
     694        ";
     695
     696        $creates = dbDelta( $schema );
     697        $this->assertSame(
     698            array(
     699                0 => "Added index {$wpdb->prefix}dbdelta_test KEY `key_2` (`column_1`(10))",
     700                1 => "Added index {$wpdb->prefix}dbdelta_test KEY `key_3` (`column_2`(100),`column_1`(10))",
     701            ),
     702            $creates
     703        );
     704
     705        $updates = dbDelta( $schema );
     706        $this->assertEmpty( $updates );
     707    }
     708
     709    /**
     710     * @ticket 34959
     711     */
     712    function test_index_col_names_with_order_do_not_recreate_indices() {
     713        global $wpdb;
     714
     715        $updates = dbDelta(
     716            "
     717            CREATE TABLE {$wpdb->prefix}dbdelta_test (
     718                id bigint(20) NOT NULL AUTO_INCREMENT,
     719                column_1 varchar(255) NOT NULL,
     720                column_2 text,
     721                column_3 blob,
     722                PRIMARY KEY  (id),
     723                KEY key_1 (column_1 DESC),
     724                KEY compound_key (id,column_1 ASC),
     725                FULLTEXT KEY fulltext_key (column_1)
     726            ) ENGINE=MyISAM
     727            "
     728        );
     729
     730        $this->assertEmpty( $updates );
     731    }
     732
     733    /**
     734     * @ticket 34873
     735     */
     736    function test_primary_key_with_single_space_does_not_recreate_index() {
     737        global $wpdb;
     738
     739        $updates = dbDelta(
     740            "
     741            CREATE TABLE {$wpdb->prefix}dbdelta_test (
     742                id bigint(20) NOT NULL AUTO_INCREMENT,
     743                column_1 varchar(255) NOT NULL,
     744                column_2 text,
     745                column_3 blob,
     746                PRIMARY KEY (id),
     747                KEY key_1 (column_1),
     748                KEY compound_key (id,column_1),
     749                FULLTEXT KEY fulltext_key (column_1)
     750            ) ENGINE=MyISAM
     751            "
     752        );
     753
     754        $this->assertEmpty( $updates );
     755    }
     756
     757    /**
     758     * @ticket 34869
     759     */
     760    function test_index_definitions_with_spaces_do_not_recreate_indices() {
     761        global $wpdb;
     762
     763        $updates = dbDelta(
     764            "
     765            CREATE TABLE {$wpdb->prefix}dbdelta_test (
     766                id bigint(20) NOT NULL AUTO_INCREMENT,
     767                column_1 varchar(255) NOT NULL,
     768                column_2 text,
     769                column_3 blob,
     770                PRIMARY KEY  (id),
     771                KEY key_1        (         column_1),
     772                KEY compound_key (id,      column_1),
     773                FULLTEXT KEY fulltext_key (column_1)
     774            ) ENGINE=MyISAM
     775            "
     776        );
     777
     778        $this->assertEmpty( $updates );
     779    }
     780
     781    /**
     782     * @ticket 34871
     783     */
     784    function test_index_types_are_not_case_sensitive_and_do_not_recreate_indices() {
     785        global $wpdb;
     786
     787        $updates = dbDelta(
     788            "
     789            CREATE TABLE {$wpdb->prefix}dbdelta_test (
     790                id bigint(20) NOT NULL AUTO_INCREMENT,
     791                column_1 varchar(255) NOT NULL,
     792                column_2 text,
     793                column_3 blob,
     794                PRIMARY KEY  (id),
     795                key key_1 (column_1),
     796                key compound_key (id,column_1),
     797                FULLTEXT KEY fulltext_key (column_1)
     798            ) ENGINE=MyISAM
     799            "
     800        );
     801
     802        $this->assertEmpty( $updates );
     803    }
    539804}
Note: See TracChangeset for help on using the changeset viewer.