dbDelta( string[]|string $queries = '', bool $execute = true )

Modifies the database based on specified SQL statements.


Description Description

Useful for creating new tables and updating existing tables to a new structure.


Parameters Parameters

$queries

(string[]|string) (Optional) The query to run. Can be multiple queries in an array, or a string of queries separated by semicolons.

Default value: ''

$execute

(bool) (Optional) Whether or not to execute the query right away.

Default value: true


Top ↑

Return Return

(array) Strings containing the results of the various update queries.


Top ↑

Source Source

File: wp-admin/includes/upgrade.php

2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
function dbDelta( $queries = '', $execute = true ) {
    global $wpdb;
 
    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.
     *
     * @since 3.3.0
     *
     * @param string[] $queries An array of dbDelta SQL queries.
     */
    $queries = apply_filters( 'dbdelta_queries', $queries );
 
    $cqueries   = array(); // Creation Queries
    $iqueries   = array(); // Insertion Queries
    $for_update = array();
 
    // 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 ) ) {
            $iqueries[] = $qry;
        } elseif ( preg_match( '|UPDATE ([^ ]*)|', $qry, $matches ) ) {
            $iqueries[] = $qry;
        } else {
            // 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".
     *
     * @since 3.3.0
     *
     * @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".
     *
     * @since 3.3.0
     *
     * @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 ) && ! wp_should_upgrade_global_tables() ) {
            unset( $cqueries[ $table ], $for_update[ $table ] );
            continue;
        }
 
        // Fetch the table column structure from the database
        $suppress    = $wpdb->suppress_errors();
        $tablefields = $wpdb->get_results( "DESCRIBE {$table};" );
        $wpdb->suppress_errors( $suppress );
 
        if ( ! $tablefields ) {
            continue;
        }
 
        // Clear the field and index arrays.
        $cfields = $indices = $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.
            $validfield = true;
            switch ( $fieldname_lowercased ) {
                case '':
                case 'primary':
                case 'index':
                case 'fulltext':
                case 'unique':
                case 'key':
                case 'spatial':
                    $validfield = false;
 
                    /*
                     * 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
                     * index information.
                     */
 
                    // Extract type, name and columns from the definition.
                    // phpcs:disable Squiz.Strings.ConcatenationSpacing.PaddingFound -- don't remove regex indentation
                    preg_match(
                        '/^'
                        .   '(?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.
                        .       '(?P<index_columns>'
                        .           '.+?'                 // 3) Column names, index prefixes, and orders.
                        .       ')'
                        .   '\)'                          // Closing bracket for the columns.
                        . '$/im',
                        $fld,
                        $index_matches
                    );
                    // phpcs:enable
 
                    // 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 = $index_columns_without_subparts = array_map( 'trim', explode( ',', $index_matches['index_columns'] ) );
 
                    // Normalize columns.
                    foreach ( $index_columns as $id => &$index_column ) {
                        // Extract column name and number of indexed characters (sub_part).
                        preg_match(
                            '/'
                            . '`?'                      // 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.
                            . '(?P<sub_part>'
                            . '\d+'         // 2) Number of indexed characters.
                            . ')'
                            . '\s*'             // Optional white space character before closing bracket.
                            . '\)'                 // Closing bracket for the sub part.
                            . ')?'
                            . '/',
                            $index_column,
                            $index_column_matches
                        );
 
                        // 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 );
 
                    break;
            }
 
            // If it's a valid field, add it to the field array.
            if ( $validfield ) {
                $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 ) {
                    $do_change = true;
                    if ( in_array( $fieldtype_lowercased, $text_fields ) && in_array( $tablefield_type_lowercased, $text_fields ) ) {
                        if ( array_search( $fieldtype_lowercased, $text_fields ) < array_search( $tablefield_type_lowercased, $text_fields ) ) {
                            $do_change = false;
                        }
                    }
 
                    if ( in_array( $fieldtype_lowercased, $blob_fields ) && in_array( $tablefield_type_lowercased, $blob_fields ) ) {
                        if ( array_search( $fieldtype_lowercased, $blob_fields ) < array_search( $tablefield_type_lowercased, $blob_fields ) ) {
                            $do_change = false;
                        }
                    }
 
                    if ( $do_change ) {
                        // 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 ] );
            } else {
                // 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};" );
 
        if ( $tableindices ) {
            // Clear the index array.
            $index_ary = array();
 
            // For every index in the table.
            foreach ( $tableindices as $tableindex ) {
 
                // Add the index to the index data array.
                $keyname                             = strtolower( $tableindex->Key_name );
                $index_ary[ $keyname ]['columns'][]  = array(
                    'fieldname' => $tableindex->Column_name,
                    'subpart'   => $tableindex->Sub_part,
                );
                $index_ary[ $keyname ]['unique']     = ( $tableindex->Non_unique == 0 ) ? true : false;
                $index_ary[ $keyname ]['index_type'] = $tableindex->Index_type;
            }
 
            // For each actual index in the index array.
            foreach ( $index_ary as $index_name => $index_data ) {
 
                // Build a create string to compare to the query.
                $index_string = '';
                if ( $index_name == 'primary' ) {
                    $index_string .= 'PRIMARY ';
                } elseif ( $index_data['unique'] ) {
                    $index_string .= 'UNIQUE ';
                }
                if ( 'FULLTEXT' === strtoupper( $index_data['index_type'] ) ) {
                    $index_string .= 'FULLTEXT ';
                }
                if ( 'SPATIAL' === strtoupper( $index_data['index_type'] ) ) {
                    $index_string .= 'SPATIAL ';
                }
                $index_string .= 'KEY ';
                if ( 'primary' !== $index_name ) {
                    $index_string .= '`' . $index_name . '`';
                }
                $index_columns = '';
 
                // For each column in the index.
                foreach ( $index_data['columns'] as $column_data ) {
                    if ( $index_columns != '' ) {
                        $index_columns .= ',';
                    }
 
                    // Add the field to the column list string.
                    $index_columns .= '`' . $column_data['fieldname'] . '`';
                }
 
                // Add the column list to the index create string.
                $index_string .= " ($index_columns)";
 
                // Check if the index definition exists, ignoring subparts.
                if ( ! ( ( $aindex = array_search( $index_string, $indices_without_subparts ) ) === false ) ) {
                    // If the index already exists (even with different subparts), we don't need to create it.
                    unset( $indices_without_subparts[ $aindex ] );
                    unset( $indices[ $aindex ] );
                }
            }
        }
 
        // For every remaining index specified for the table.
        foreach ( (array) $indices as $index ) {
            // Push a query line into $cqueries that adds the index to that table.
            $cqueries[]   = "ALTER TABLE {$table} ADD $index";
            $for_update[] = 'Added index ' . $table . ' ' . $index;
        }
 
        // Remove the original table creation query from processing.
        unset( $cqueries[ $table ], $for_update[ $table ] );
    }
 
    $allqueries = array_merge( $cqueries, $iqueries );
    if ( $execute ) {
        foreach ( $allqueries as $query ) {
            $wpdb->query( $query );
        }
    }
 
    return $for_update;
}

Top ↑

Changelog Changelog

Changelog
Version Description
1.5.0 Introduced.


Top ↑

User Contributed Notes User Contributed Notes

  1. Skip to note 2 content

    You must be very careful in your SQL command structure when creating tables with indexes.

    Here is a simple example of the proper create table syntax for a table with a primary key on a field named “id” and a secondary key on a field named “first”.

    PRIMARY KEY must be followed by TWO SPACES then the open parenthesis then the field name and a closing parenthesis.

    KEY must be followed by a SINGLE SPACE then the key name then a space then open parenthesis with the field name then a closed parenthesis.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    private function index_test_001() {
         global $wpdb;
         $table_name = $wpdb->prefix . 'dbdelta_test_001';
         $wpdb_collate = $wpdb->collate;
         $sql =
             "CREATE TABLE {$table_name} (
             id mediumint(8) unsigned NOT NULL auto_increment ,
             first varchar(255) NULL,
             PRIMARY KEY  (id),
             KEY first (first)
             )
             COLLATE {$wpdb_collate}";
     
         require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
         dbDelta( $sql );
     }
  2. Skip to note 3 content
    Contributed by Earnest Boyd

    Be careful not to put a COMMENT on field or key; the preg_match code doesn’t handle it. The following code is wrong (thanks to Store Locator Plus’ code).

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    private function index_test_001() {
         global $wpdb;
         $table_name = $wpdb->prefix . 'dbdelta_test_001';
         $wpdb_collate = $wpdb->collate;
         $sql =
             "CREATE TABLE {$table_name} (
             id mediumint(8) unsigned NOT NULL auto_increment ,
             first varchar(255) NULL,
             PRIMARY KEY  (id),
             KEY first (first) COMMENT 'First name'
             )
             COLLATE {$wpdb_collate}";
      
         require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
         dbDelta( $sql );
     }
  3. Skip to note 4 content
    Contributed by octag

    (I post here a corrected version of my previous note. Several typographical mistakes have slipped into the original version.)
    As a side-note, the dbDelta function cannot be used to drop a table from the wp_ database . A function such as the one below can be used instead (don’t forget to replace my_theme with your own theme name):

    1
    2
    3
    4
    5
    6
    7
    8
    function my_theme_drop_table ( $table_name = 'the_name_without_any_prefix' ){
        global $wpdb;
     
        $table_name_prepared = $wpdb->prefix . $table_name;
        $the_removal_query = "DROP TABLE IF EXISTS {$table_name_prepared}";
     
        $wpdb->query( $the_removal_query );
    }

    See also https://developer.wordpress.org/plugins/the-basics/uninstall-methods/.

You must log in before being able to contribute a note or feedback.