Query: Account for primed post caches without primed post meta/term caches.

In [54352] `update_post_caches()` was replaced by `_prime_post_caches()` to reduce excessive object cache calls. That's because `_prime_post_caches()` checks first if post IDs aren't already cached. Unfortunately this becomes an issue if a post itself is cached but not the meta/terms.
To fix this regression, `_prime_post_caches()` now always calls `update_postmeta_cache()` and `update_object_term_cache()` depending on the arguments passed to it. Both functions internally check whether IDs are already cached so the fix from [54352] remains in place.

Props peterwilsoncc, spacedmonkey, ocean90.
Fixes #57163.

git-svn-id: https://develop.svn.wordpress.org/branches/6.1@54892 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Dominik Schilling 2022-11-29 20:09:24 +00:00
parent 360f6ed32d
commit a662739940
3 changed files with 327 additions and 5 deletions

View File

@ -7822,6 +7822,7 @@ function wp_delete_auto_drafts() {
function wp_queue_posts_for_term_meta_lazyload( $posts ) {
$post_type_taxonomies = array();
$term_ids = array();
$prime_post_terms = array();
foreach ( $posts as $post ) {
if ( ! ( $post instanceof WP_Post ) ) {
continue;
@ -7831,6 +7832,16 @@ function wp_queue_posts_for_term_meta_lazyload( $posts ) {
$post_type_taxonomies[ $post->post_type ] = get_object_taxonomies( $post->post_type );
}
foreach ( $post_type_taxonomies[ $post->post_type ] as $taxonomy ) {
$prime_post_terms[ $taxonomy ][] = $post->ID;
}
}
foreach ( $prime_post_terms as $taxonomy => $post_ids ){
wp_cache_get_multiple( $post_ids, "{$taxonomy}_relationships" );
}
foreach ( $posts as $post ) {
foreach ( $post_type_taxonomies[ $post->post_type ] as $taxonomy ) {
// Term cache should already be primed by `update_post_term_cache()`.
$terms = get_object_term_cache( $post->ID, $taxonomy );
@ -7877,7 +7888,9 @@ function _update_term_count_on_transition_post_status( $new_status, $old_status,
* @since 3.4.0
* @since 6.1.0 This function is no longer marked as "private".
*
* @see update_post_caches()
* @see update_post_cache()
* @see update_postmeta_cache()
* @see update_object_term_cache()
*
* @global wpdb $wpdb WordPress database abstraction object.
*
@ -7892,7 +7905,20 @@ function _prime_post_caches( $ids, $update_term_cache = true, $update_meta_cache
if ( ! empty( $non_cached_ids ) ) {
$fresh_posts = $wpdb->get_results( sprintf( "SELECT $wpdb->posts.* FROM $wpdb->posts WHERE ID IN (%s)", implode( ',', $non_cached_ids ) ) );
update_post_caches( $fresh_posts, 'any', $update_term_cache, $update_meta_cache );
if ( $fresh_posts ) {
// Despite the name, update_post_cache() expects an array rather than a single post.
update_post_cache( $fresh_posts );
}
}
if ( $update_meta_cache ) {
update_postmeta_cache( $ids );
}
if ( $update_term_cache ) {
$post_types = array_map( 'get_post_type', $ids );
$post_types = array_unique( $post_types );
update_object_term_cache( $ids, $post_types );
}
}

View File

@ -0,0 +1,229 @@
<?php
/**
* Test `_prime_post_caches()`.
*
* @package WordPress
*/
/**
* Test class for `_prime_post_caches()`.
*
* @group post
* @group cache
*
* @covers ::_prime_post_caches
*/
class Tests_Post_PrimePostCaches extends WP_UnitTestCase {
/**
* Post IDs.
*
* @var int[]
*/
public static $posts;
/**
* Set up test resources before the class.
*
* @param WP_UnitTest_Factory $factory The unit test factory.
*/
public static function wpSetupBeforeClass( WP_UnitTest_Factory $factory ) {
self::$posts = $factory->post->create_many( 3 );
$category = $factory->term->create(
array(
'taxonomy' => 'category',
'slug' => 'foo',
'name' => 'Foo',
)
);
wp_set_post_terms( self::$posts[0], $category, 'category' );
add_post_meta( self::$posts[0], 'meta', 'foo' );
add_post_meta( self::$posts[1], 'meta', 'bar' );
}
/**
* @ticket 57163
*/
public function test_prime_post_caches() {
$post_id = self::$posts[0];
$this->assertSame( array( $post_id ), _get_non_cached_ids( array( $post_id ), 'posts' ), 'Post is already cached.' );
// Test posts cache.
$before_num_queries = get_num_queries();
_prime_post_caches( array( $post_id ) );
$num_queries = get_num_queries() - $before_num_queries;
/*
* Four expected queries:
* 1: Posts data,
* 2: Post meta data,
* 3: Taxonomy data,
* 4: Term data.
*/
$this->assertSame( 4, $num_queries, 'Unexpected number of queries.' );
$this->assertSame( array(), _get_non_cached_ids( array( $post_id ), 'posts' ), 'Post is not cached.' );
// Test post meta cache.
$before_num_queries = get_num_queries();
$meta = get_post_meta( $post_id, 'meta', true );
$num_queries = get_num_queries() - $before_num_queries;
$this->assertSame( 'foo', $meta, 'Meta has unexpected value.' );
$this->assertSame( 0, $num_queries, 'Unexpected number of queries.' );
// Test term cache.
$before_num_queries = get_num_queries();
$categories = get_the_category( $post_id );
$num_queries = get_num_queries() - $before_num_queries;
$this->assertNotEmpty( $categories, 'Categories does return an empty result set.' );
$this->assertSame( 0, $num_queries, 'Unexpected number of queries.' );
}
/**
* @ticket 57163
*/
public function test_prime_post_caches_with_multiple_posts() {
$this->assertSame( self::$posts, _get_non_cached_ids( self::$posts, 'posts' ), 'Posts are already cached.' );
$before_num_queries = get_num_queries();
_prime_post_caches( self::$posts );
$num_queries = get_num_queries() - $before_num_queries;
/*
* Four expected queries:
* 1: Posts data,
* 2: Post meta data,
* 3: Taxonomy data,
* 4: Term data.
*/
$this->assertSame( 4, $num_queries, 'Unexpected number of queries.' );
$this->assertSame( array(), _get_non_cached_ids( self::$posts, 'posts' ), 'Posts are not cached.' );
}
/**
* @ticket 57163
*/
public function test_prime_post_caches_only_posts_cache() {
$this->assertSame( self::$posts, _get_non_cached_ids( self::$posts, 'posts' ), 'Posts are already cached.' );
$before_num_queries = get_num_queries();
_prime_post_caches( self::$posts, false, false );
$num_queries = get_num_queries() - $before_num_queries;
/*
* One expected query:
* 1: Posts data.
*/
$this->assertSame( 1, $num_queries, 'Unexpected number of queries.' );
$this->assertSame( array(), _get_non_cached_ids( self::$posts, 'posts' ), 'Posts are not cached.' );
}
/**
* @ticket 57163
*/
public function test_prime_post_caches_only_posts_and_term_cache() {
$this->assertSame( self::$posts, _get_non_cached_ids( self::$posts, 'posts' ), 'Posts are already cached.' );
$before_num_queries = get_num_queries();
_prime_post_caches( self::$posts, true, false );
$num_queries = get_num_queries() - $before_num_queries;
/*
* Three expected queries:
* 1: Posts data.
* 2: Taxonomy data,
* 3: Term data.
*/
$this->assertSame( 3, $num_queries, 'Unexpected number of queries.' );
$this->assertSame( array(), _get_non_cached_ids( self::$posts, 'posts' ), 'Posts are not cached.' );
// Test term cache.
$before_num_queries = get_num_queries();
$categories = get_the_category( self::$posts[0] );
$num_queries = get_num_queries() - $before_num_queries;
$this->assertNotEmpty( $categories, 'Categories does return an empty result set.' );
$this->assertSame( 0, $num_queries, 'Unexpected number of queries.' );
}
/**
* @ticket 57163
*/
public function test_prime_post_caches_only_posts_and_meta_cache() {
$this->assertSame( self::$posts, _get_non_cached_ids( self::$posts, 'posts' ), 'Posts are already cached.' );
$before_num_queries = get_num_queries();
_prime_post_caches( self::$posts, false, true );
$num_queries = get_num_queries() - $before_num_queries;
/*
* Two expected queries:
* 1: Posts data.
* 2: Post meta data.
*/
$this->assertSame( 2, $num_queries, 'Unexpected number of queries warming cache.' );
$this->assertSame( array(), _get_non_cached_ids( self::$posts, 'posts' ), 'Posts are not cached.' );
// Test post meta cache.
$before_num_queries = get_num_queries();
$meta_1 = get_post_meta( self::$posts[0], 'meta', true );
$meta_2 = get_post_meta( self::$posts[1], 'meta', true );
$num_queries = get_num_queries() - $before_num_queries;
$this->assertSame( 'foo', $meta_1, 'Meta 1 has unexpected value.' );
$this->assertSame( 'bar', $meta_2, 'Meta 2 has unexpected value.' );
$this->assertSame( 0, $num_queries, 'Unexpected number of queries getting post meta.' );
}
/**
* @ticket 57163
*/
public function test_prime_post_caches_accounts_for_posts_without_primed_meta_terms() {
$post_id = self::$posts[0];
$this->assertSame( array( $post_id ), _get_non_cached_ids( array( $post_id ), 'posts' ), 'Post is already cached.' );
// Warm only the posts cache.
$post = get_post( $post_id );
$this->assertNotEmpty( $post, 'Post does not exist.' );
$this->assertEmpty( _get_non_cached_ids( array( $post_id ), 'posts' ), 'Post is not cached.' );
$before_num_queries = get_num_queries();
_prime_post_caches( array( $post_id ) );
$num_queries = get_num_queries() - $before_num_queries;
/*
* Three expected queries:
* 1: Post meta data,
* 2: Taxonomy data,
* 3: Term data.
*/
$this->assertSame( 3, $num_queries, 'Unexpected number of queries.' );
}
/**
* @ticket 57163
*/
public function test_prime_post_caches_does_not_prime_caches_twice() {
$this->assertSame( self::$posts, _get_non_cached_ids( self::$posts, 'posts' ), 'Posts are already cached.' );
_prime_post_caches( self::$posts );
$this->assertSame( array(), _get_non_cached_ids( self::$posts, 'posts' ), 'Posts are not cached.' );
$before_num_queries = get_num_queries();
_prime_post_caches( self::$posts );
$num_queries = get_num_queries() - $before_num_queries;
$this->assertSame( 0, $num_queries, 'Unexpected number of queries.' );
}
}

View File

@ -1237,9 +1237,11 @@ class Test_Query_CacheResults extends WP_UnitTestCase {
$query_1 = new WP_Query(
array(
'post_type' => 'page',
'fields' => $fields,
'author' => self::$author_id,
'post_type' => 'page',
'fields' => $fields,
'author' => self::$author_id,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
)
);
@ -1247,6 +1249,11 @@ class Test_Query_CacheResults extends WP_UnitTestCase {
$start_loop_queries = get_num_queries();
$query_1->the_post();
$num_loop_queries = get_num_queries() - $start_loop_queries;
/*
* Two expected queries:
* 1: User meta data,
* 2: User data.
*/
$this->assertSame( 2, $num_loop_queries, 'Unexpected number of queries while initializing the loop.' );
$start_author_queries = get_num_queries();
@ -1271,4 +1278,64 @@ class Test_Query_CacheResults extends WP_UnitTestCase {
*/
);
}
/**
* Ensure lazy loading term meta queries all term meta in a single query.
*
* @since 6.1.2
* @ticket 57163
* @ticket 22176
*/
public function test_get_post_meta_lazy_loads_all_term_meta_data() {
$query = new WP_Query();
$t2 = $this->factory()->term->create(
array(
'taxonomy' => 'category',
'slug' => 'bar',
'name' => 'Bar',
)
);
wp_set_post_terms( self::$posts[0], $t2, 'category', true );
// Clean data added to cache by factory and setting terms.
clean_term_cache( array( self::$t1, $t2 ), 'category' );
clean_post_cache( self::$posts[0] );
$num_queries_start = get_num_queries();
$query_posts = $query->query(
array(
'lazy_load_term_meta' => true,
'no_found_rows' => true,
)
);
$num_queries = get_num_queries() - $num_queries_start;
/*
* Four expected queries:
* 1: Post IDs
* 2: Post data
* 3: Post meta data.
* 4: Post term data.
*/
$this->assertSame( 4, $num_queries, 'Unexpected number of queries while querying posts.' );
$this->assertNotEmpty( $query_posts, 'Query posts is empty.' );
$num_queries_start = get_num_queries();
get_term_meta( self::$t1 );
$num_queries = get_num_queries() - $num_queries_start;
/*
* One expected query:
* 1: Term meta data.
*/
$this->assertSame( 1, $num_queries, 'Unexpected number of queries during first query of term meta.' );
$num_queries_start = get_num_queries();
get_term_meta( $t2 );
$num_queries = get_num_queries() - $num_queries_start;
// No additional queries expected.
$this->assertSame( 0, $num_queries, 'Unexpected number of queries during second query of term meta.' );
}
}