Query: Fix performance regression starting the loop for all fields.
Some checks are pending
Cleanup Pull Requests / Clean up pull requests (push) Waiting to run
Coding Standards / PHP coding standards (push) Waiting to run
Coding Standards / JavaScript coding standards (push) Waiting to run
Coding Standards / Slack Notifications (push) Blocked by required conditions
Coding Standards / Failed workflow tasks (push) Blocked by required conditions
End-to-end Tests / Test with SCRIPT_DEBUG disabled (push) Waiting to run
End-to-end Tests / Test with SCRIPT_DEBUG enabled (push) Waiting to run
End-to-end Tests / Slack Notifications (push) Blocked by required conditions
End-to-end Tests / Failed workflow tasks (push) Blocked by required conditions
JavaScript Tests / QUnit Tests (push) Waiting to run
JavaScript Tests / Slack Notifications (push) Blocked by required conditions
JavaScript Tests / Failed workflow tasks (push) Blocked by required conditions
Performance Tests / Determine Matrix (push) Waiting to run
Performance Tests / ${{ matrix.multisite && 'Multisite' || 'Single Site' }} ${{ matrix.memcached && 'Memcached' || 'Default' }} (push) Blocked by required conditions
Performance Tests / Compare (push) Blocked by required conditions
Performance Tests / Slack Notifications (push) Blocked by required conditions
Performance Tests / Failed workflow tasks (push) Blocked by required conditions
PHP Compatibility / Check PHP compatibility (push) Waiting to run
PHP Compatibility / Slack Notifications (push) Blocked by required conditions
PHP Compatibility / Failed workflow tasks (push) Blocked by required conditions
PHPUnit Tests / PHP 7.2 (push) Waiting to run
PHPUnit Tests / PHP 7.3 (push) Waiting to run
PHPUnit Tests / PHP 7.4 (push) Waiting to run
PHPUnit Tests / PHP 8.0 (push) Waiting to run
PHPUnit Tests / PHP 8.1 (push) Waiting to run
PHPUnit Tests / PHP 8.2 (push) Waiting to run
PHPUnit Tests / PHP 8.3 (push) Waiting to run
PHPUnit Tests / PHP 8.4 (push) Waiting to run
PHPUnit Tests / html-api-html5lib-tests (push) Waiting to run
PHPUnit Tests / Slack Notifications (push) Blocked by required conditions
PHPUnit Tests / Failed workflow tasks (push) Blocked by required conditions
Test Build Processes / Core running from build (push) Waiting to run
Test Build Processes / Core running from src (push) Waiting to run
Test Build Processes / Gutenberg running from build (push) Waiting to run
Test Build Processes / Gutenberg running from src (push) Waiting to run
Test Build Processes / Slack Notifications (push) Blocked by required conditions
Test Build Processes / Failed workflow tasks (push) Blocked by required conditions
Upgrade Develop Version Tests / Build (push) Waiting to run
Upgrade Develop Version Tests / Upgrade from 4.9 (push) Blocked by required conditions
Upgrade Develop Version Tests / Upgrade from 6.5 (push) Blocked by required conditions
Upgrade Develop Version Tests / Upgrade from 6.6 (push) Blocked by required conditions
Upgrade Develop Version Tests / Upgrade from 6.7 (push) Blocked by required conditions
Upgrade Develop Version Tests / Slack Notifications (push) Blocked by required conditions
Upgrade Develop Version Tests / Failed workflow tasks (push) Blocked by required conditions

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
This commit is contained in:
Peter Wilson 2025-03-16 22:55:11 +00:00
parent 7950bbed13
commit b98b347504
2 changed files with 125 additions and 27 deletions

View File

@ -2067,6 +2067,15 @@ class WP_Query {
case 'id=>parent':
$fields = "{$wpdb->posts}.ID, {$wpdb->posts}.post_parent";
break;
case '':
/*
* Set the default to 'all'.
*
* This is used in `WP_Query::the_post` to determine if the
* entire post object has been queried.
*/
$q['fields'] = 'all';
// Falls through.
default:
$fields = "{$wpdb->posts}.*";
}
@ -3739,28 +3748,30 @@ class WP_Query {
global $post;
if ( ! $this->in_the_loop ) {
// Get post IDs to prime incomplete post objects.
$post_ids = array_reduce(
$this->posts,
function ( $carry, $post ) {
if ( is_numeric( $post ) && $post > 0 ) {
// Query for post ID.
$carry[] = $post;
}
if ( 'all' === $this->query_vars['fields'] ) {
// Full post objects queried.
$post_objects = $this->posts;
} else {
if ( 'ids' === $this->query_vars['fields'] ) {
// Post IDs queried.
$post_ids = $this->posts;
} else {
// Only partial objects queried, need to prime the cache for the loop.
$post_ids = array_reduce(
$this->posts,
function ( $carry, $post ) {
if ( isset( $post->ID ) ) {
$carry[] = $post->ID;
}
if ( is_object( $post ) && isset( $post->ID ) ) {
// Query for object, either WP_Post or stdClass.
$carry[] = $post->ID;
}
return $carry;
},
array()
);
if ( $post_ids ) {
return $carry;
},
array()
);
}
_prime_post_caches( $post_ids, $this->query_vars['update_post_term_cache'], $this->query_vars['update_post_meta_cache'] );
$post_objects = array_map( 'get_post', $post_ids );
}
$post_objects = array_map( 'get_post', $post_ids );
update_post_author_caches( $post_objects );
}
@ -3781,12 +3792,19 @@ class WP_Query {
$post = $this->next_post();
// Ensure a full post object is available.
if ( $post instanceof stdClass ) {
// stdClass indicates that a partial post object was queried.
$post = get_post( $post->ID );
} elseif ( is_numeric( $post ) ) {
// Numeric indicates that only post IDs were queried.
$post = get_post( $post );
if ( 'all' !== $this->query_vars['fields'] ) {
if ( 'ids' === $this->query_vars['fields'] ) {
// Post IDs queried.
$post = get_post( $post );
} elseif ( isset( $post->ID ) ) {
/*
* Partial objecct queried.
*
* The post object was queried with a partial set of
* fields, populate the entire object for the loop.
*/
$post = get_post( $post->ID );
}
}
// Set up the global post object for the loop.

View File

@ -48,6 +48,86 @@ class Tests_Query_ThePost extends WP_UnitTestCase {
}
}
/**
* Ensure custom 'fields' values are respected.
*
* @ticket 56992
*/
public function test_wp_query_respects_custom_fields_values() {
global $wpdb;
add_filter(
'posts_fields',
function ( $fields, $query ) {
global $wpdb;
if ( $query->get( 'fields' ) === 'custom' ) {
$fields = "$wpdb->posts.ID,$wpdb->posts.post_author";
}
return $fields;
},
10,
2
);
$query = new WP_Query(
array(
'fields' => 'custom',
'post_type' => 'page',
'post__in' => self::$page_child_ids,
)
);
$this->assertNotEmpty( $query->posts, 'The query is expected to return results' );
$this->assertSame( $query->get( 'fields' ), 'custom', 'The WP_Query class is expected to use the custom fields value' );
$this->assertStringContainsString( "$wpdb->posts.ID,$wpdb->posts.post_author", $query->request, 'The database query is expected to use the custom fields value' );
}
/**
* Ensure custom 'fields' populates the global post in the loop.
*
* @ticket 56992
*/
public function test_wp_query_with_custom_fields_value_populates_the_global_post() {
global $wpdb;
add_filter(
'posts_fields',
function ( $fields, $query ) {
global $wpdb;
if ( $query->get( 'fields' ) === 'custom' ) {
$fields = "$wpdb->posts.ID,$wpdb->posts.post_author";
}
return $fields;
},
10,
2
);
$query = new WP_Query(
array(
'fields' => 'custom',
'post_type' => 'page',
'post__in' => self::$page_child_ids,
'orderby' => 'id',
'order' => 'ASC',
)
);
$query->the_post();
// Get the global post and specific post.
$global_post = get_post();
$specific_post = get_post( self::$page_child_ids[0], ARRAY_A );
$this->assertSameSetsWithIndex( $specific_post, $global_post->to_array(), 'The global post is expected to be fully populated.' );
$this->assertNotEmpty( get_the_title(), 'The title is expected to be populated.' );
$this->assertNotEmpty( get_the_content(), 'The content is expected to be populated.' );
$this->assertNotEmpty( get_the_excerpt(), 'The excerpt is expected to be populated.' );
}
/**
* Ensure that a secondary loop populates the global post completely regardless of the fields parameter.
*
@ -75,11 +155,11 @@ class Tests_Query_ThePost extends WP_UnitTestCase {
$global_post = get_post();
$specific_post = get_post( self::$page_child_ids[0], ARRAY_A );
$this->assertSameSetsWithIndex( $specific_post, $global_post->to_array(), 'The global post is expected to be fully populated.' );
$this->assertNotEmpty( get_the_title(), 'The title is expected to be populated.' );
$this->assertNotEmpty( get_the_content(), 'The content is expected to be populated.' );
$this->assertNotEmpty( get_the_excerpt(), 'The excerpt is expected to be populated.' );
$this->assertSameSetsWithIndex( $specific_post, $global_post->to_array(), 'The global post is expected to be fully populated.' );
}
/**