WordPress.org

Make WordPress Core

Changeset 37583


Ignore:
Timestamp:
05/27/16 11:56:20 (4 months 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.