From 35421f2b075261ea23a13948e18ecec87df170dc Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Tue, 29 Nov 2022 20:27:29 +0000 Subject: [PATCH] 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/trunk@54894 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/post.php | 19 +- tests/phpunit/tests/post/primePostCaches.php | 229 +++++++++++++++++++ tests/phpunit/tests/query/cacheResults.php | 73 +++++- 3 files changed, 316 insertions(+), 5 deletions(-) create mode 100644 tests/phpunit/tests/post/primePostCaches.php diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 1163d023cd..82eea18e7f 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -7876,7 +7876,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. * @@ -7891,7 +7893,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 ); } } diff --git a/tests/phpunit/tests/post/primePostCaches.php b/tests/phpunit/tests/post/primePostCaches.php new file mode 100644 index 0000000000..ab99a2d322 --- /dev/null +++ b/tests/phpunit/tests/post/primePostCaches.php @@ -0,0 +1,229 @@ +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.' ); + } +} diff --git a/tests/phpunit/tests/query/cacheResults.php b/tests/phpunit/tests/query/cacheResults.php index 6e2dde1f0f..4ac68ceb4e 100644 --- a/tests/phpunit/tests/query/cacheResults.php +++ b/tests/phpunit/tests/query/cacheResults.php @@ -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.' ); + } }