Skip to content

Commit b98b347

Browse files
committed
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. git-svn-id: https://develop.svn.wordpress.org/trunk@59993 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 7950bbe commit b98b347

File tree

2 files changed

+125
-27
lines changed

2 files changed

+125
-27
lines changed

src/wp-includes/class-wp-query.php

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2067,6 +2067,15 @@ public function get_posts() {
20672067
case 'id=>parent':
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}.*";
20722081
}
@@ -3739,28 +3748,30 @@ public function the_post() {
37393748
global $post;
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-
}
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+
}
37553766

3756-
return $carry;
3757-
},
3758-
array()
3759-
);
3760-
if ( $post_ids ) {
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'] );
3773+
$post_objects = array_map( 'get_post', $post_ids );
37623774
}
3763-
$post_objects = array_map( 'get_post', $post_ids );
37643775
update_post_author_caches( $post_objects );
37653776
}
37663777

@@ -3781,12 +3792,19 @@ function ( $carry, $post ) {
37813792
$post = $this->next_post();
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

37923810
// Set up the global post object for the loop.

tests/phpunit/tests/query/thePost.php

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,86 @@ public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) {
4848
}
4949
}
5050

51+
/**
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+
51131
/**
52132
* Ensure that a secondary loop populates the global post completely regardless of the fields parameter.
53133
*
@@ -75,11 +155,11 @@ public function test_the_loop_populates_the_global_post_completely( $fields ) {
75155
$global_post = get_post();
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

85165
/**

0 commit comments

Comments
 (0)