Make WordPress Core

Changeset 59993


Ignore:
Timestamp:
03/16/2025 10:55:11 PM (19 months ago)
Author:
peterwilsoncc
Message:

Query: Fix performance regression starting the loop for all fields.

Fixes a performance regression starting the loop after calling WP_Query( [ 'fields' => 'all' ] ). This changes how WP_Query::the_post() determines whether there is a need to traverse the posts for cache warming.

If IDs are queried, WP_Query::$posts is assumed to be an array of post IDs. If all fields are queried, WP_Query::$posts is assumed to be an array of fully populated post objects.

Follow up to [59919], [59937].

Props joemcgill, peterwilsoncc, SirLouen.
Fixes #56992.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-query.php

    r59976 r59993  
    20682068                                $fields = "{$wpdb->posts}.ID, {$wpdb->posts}.post_parent";
    20692069                                break;
     2070                        case '':
     2071                                /*
     2072                                 * Set the default to 'all'.
     2073                                 *
     2074                                 * This is used in `WP_Query::the_post` to determine if the
     2075                                 * entire post object has been queried.
     2076                                 */
     2077                                $q['fields'] = 'all';
     2078                                // Falls through.
    20702079                        default:
    20712080                                $fields = "{$wpdb->posts}.*";
    … …  
    37403749
    37413750                if ( ! $this->in_the_loop ) {
    3742                         // Get post IDs to prime incomplete post objects.
    3743                         $post_ids = array_reduce(
    3744                                 $this->posts,
    3745                                 function ( $carry, $post ) {
    3746                                         if ( is_numeric( $post ) && $post > 0 ) {
    3747                                                 // Query for post ID.
    3748                                                 $carry[] = $post;
    3749                                         }
    3750 
    3751                                         if ( is_object( $post ) && isset( $post->ID ) ) {
    3752                                                 // Query for object, either WP_Post or stdClass.
    3753                                                 $carry[] = $post->ID;
    3754                                         }
    3755 
    3756                                         return $carry;
    3757                                 },
    3758                                 array()
    3759                         );
    3760                         if ( $post_ids ) {
     3751                        if ( 'all' === $this->query_vars['fields'] ) {
     3752                                // Full post objects queried.
     3753                                $post_objects = $this->posts;
     3754                        } else {
     3755                                if ( 'ids' === $this->query_vars['fields'] ) {
     3756                                        // Post IDs queried.
     3757                                        $post_ids = $this->posts;
     3758                                } else {
     3759                                        // Only partial objects queried, need to prime the cache for the loop.
     3760                                        $post_ids = array_reduce(
     3761                                                $this->posts,
     3762                                                function ( $carry, $post ) {
     3763                                                        if ( isset( $post->ID ) ) {
     3764                                                                $carry[] = $post->ID;
     3765                                                        }
     3766
     3767                                                        return $carry;
     3768                                                },
     3769                                                array()
     3770                                        );
     3771                                }
    37613772                                _prime_post_caches( $post_ids, $this->query_vars['update_post_term_cache'], $this->query_vars['update_post_meta_cache'] );
    3762                         }
    3763                         $post_objects = array_map( 'get_post', $post_ids );
     3773                                $post_objects = array_map( 'get_post', $post_ids );
     3774                        }
    37643775                        update_post_author_caches( $post_objects );
    37653776                }
    … …  
    37823793
    37833794                // Ensure a full post object is available.
    3784                 if ( $post instanceof stdClass ) {
    3785                         // stdClass indicates that a partial post object was queried.
    3786                         $post = get_post( $post->ID );
    3787                 } elseif ( is_numeric( $post ) ) {
    3788                         // Numeric indicates that only post IDs were queried.
    3789                         $post = get_post( $post );
     3795                if ( 'all' !== $this->query_vars['fields'] ) {
     3796                        if ( 'ids' === $this->query_vars['fields'] ) {
     3797                                // Post IDs queried.
     3798                                $post = get_post( $post );
     3799                        } elseif ( isset( $post->ID ) ) {
     3800                                /*
     3801                                 * Partial objecct queried.
     3802                                 *
     3803                                 * The post object was queried with a partial set of
     3804                                 * fields, populate the entire object for the loop.
     3805                                 */
     3806                                $post = get_post( $post->ID );
     3807                        }
    37903808                }
    37913809
  • trunk/tests/phpunit/tests/query/thePost.php

    r59937 r59993  
    5050
    5151        /**
     52         * Ensure custom 'fields' values are respected.
     53         *
     54         * @ticket 56992
     55         */
     56        public function test_wp_query_respects_custom_fields_values() {
     57                global $wpdb;
     58                add_filter(
     59                        'posts_fields',
     60                        function ( $fields, $query ) {
     61                                global $wpdb;
     62
     63                                if ( $query->get( 'fields' ) === 'custom' ) {
     64                                        $fields = "$wpdb->posts.ID,$wpdb->posts.post_author";
     65                                }
     66
     67                                return $fields;
     68                        },
     69                        10,
     70                        2
     71                );
     72
     73                $query = new WP_Query(
     74                        array(
     75                                'fields'    => 'custom',
     76                                'post_type' => 'page',
     77                                'post__in'  => self::$page_child_ids,
     78                        )
     79                );
     80
     81                $this->assertNotEmpty( $query->posts, 'The query is expected to return results' );
     82                $this->assertSame( $query->get( 'fields' ), 'custom', 'The WP_Query class is expected to use the custom fields value' );
     83                $this->assertStringContainsString( "$wpdb->posts.ID,$wpdb->posts.post_author", $query->request, 'The database query is expected to use the custom fields value' );
     84        }
     85
     86        /**
     87         * Ensure custom 'fields' populates the global post in the loop.
     88         *
     89         * @ticket 56992
     90         */
     91        public function test_wp_query_with_custom_fields_value_populates_the_global_post() {
     92                global $wpdb;
     93                add_filter(
     94                        'posts_fields',
     95                        function ( $fields, $query ) {
     96                                global $wpdb;
     97
     98                                if ( $query->get( 'fields' ) === 'custom' ) {
     99                                        $fields = "$wpdb->posts.ID,$wpdb->posts.post_author";
     100                                }
     101
     102                                return $fields;
     103                        },
     104                        10,
     105                        2
     106                );
     107
     108                $query = new WP_Query(
     109                        array(
     110                                'fields'    => 'custom',
     111                                'post_type' => 'page',
     112                                'post__in'  => self::$page_child_ids,
     113                                'orderby'   => 'id',
     114                                'order'     => 'ASC',
     115                        )
     116                );
     117
     118                $query->the_post();
     119
     120                // Get the global post and specific post.
     121                $global_post   = get_post();
     122                $specific_post = get_post( self::$page_child_ids[0], ARRAY_A );
     123
     124                $this->assertSameSetsWithIndex( $specific_post, $global_post->to_array(), 'The global post is expected to be fully populated.' );
     125
     126                $this->assertNotEmpty( get_the_title(), 'The title is expected to be populated.' );
     127                $this->assertNotEmpty( get_the_content(), 'The content is expected to be populated.' );
     128                $this->assertNotEmpty( get_the_excerpt(), 'The excerpt is expected to be populated.' );
     129        }
     130
     131        /**
    52132         * Ensure that a secondary loop populates the global post completely regardless of the fields parameter.
    53133         *
    … …  
    76156                $specific_post = get_post( self::$page_child_ids[0], ARRAY_A );
    77157
     158                $this->assertSameSetsWithIndex( $specific_post, $global_post->to_array(), 'The global post is expected to be fully populated.' );
     159
    78160                $this->assertNotEmpty( get_the_title(), 'The title is expected to be populated.' );
    79161                $this->assertNotEmpty( get_the_content(), 'The content is expected to be populated.' );
    80162                $this->assertNotEmpty( get_the_excerpt(), 'The excerpt is expected to be populated.' );
    81 
    82                 $this->assertSameSetsWithIndex( $specific_post, $global_post->to_array(), 'The global post is expected to be fully populated.' );
    83163        }
    84164
Note: See TracChangeset for help on using the changeset viewer.